diff --git a/docs/indexes/datasets_catalog.json b/docs/indexes/datasets_catalog.json index 28154341..31d817a8 100644 --- a/docs/indexes/datasets_catalog.json +++ b/docs/indexes/datasets_catalog.json @@ -1,7 +1,7 @@ { "schema_version": 1, - "generated_at": "2026-06-16T17:54:56+00:00", - "count": 104, + "generated_at": "2026-06-30T21:29:19+00:00", + "count": 110, "datasets": [ { "dataset_id": "etf_dynamic_rebalance_backtest_runs", @@ -1316,6 +1316,86 @@ "owner": "research-platform", "lifecycle": "active" }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "fingerprint": "sha256:4eabaa7858e197ea1d9e60e733291c4c5c23b8822ba65db20f3dba4dd9992f11", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "fingerprint": "sha256:2a9db19d253f131c38dff3e91f28fbdd1251f19f1a9fc3c0f11ac0f1333c4b3a", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "fingerprint": "sha256:cd9d4301f3c996c294a5f0b66962a6c053d831ef2a0ddd1914746ad9be1105c5", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "superseded", + "superseded_by": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "superseded_reason": "initial no-trade run; s04 is the active reproducible weekend-close variant evidence" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "fingerprint": "sha256:6c349f1a89a62304482c4289e10433ad831b4ad6ffc42bb3326ced70e785ac96", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "fingerprint": "sha256:cc0f052868017cb487d7811ee5e4de461f0af77b9459546ccc69be8a3c5c82ca", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + "fingerprint": "sha256:a3006b9f096754d0b2876f2138ccba09d0a23e63cffc91a6c3214b73361e0bbb", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, { "dataset_id": "etf_factor_rotation_baseline_audit", "snapshot_id": "2026-05-17T17-36-27Z_56361cf8a32e", diff --git a/docs/indexes/docs_catalog.json b/docs/indexes/docs_catalog.json index eaa7b6bc..735f47a8 100644 --- a/docs/indexes/docs_catalog.json +++ b/docs/indexes/docs_catalog.json @@ -1,7 +1,7 @@ { "schema_version": 1, "catalog_type": "docs", - "generated_at": "2026-06-16T17:54:56+00:00", + "generated_at": "2026-06-30T21:29:19+00:00", "count": 37, "reports": [ { @@ -28,7 +28,7 @@ "docs/rules/governance.md", "openspec/changes/archive/adr-0007-pr-flow-closed-loop-review-evidence" ], - "updated_at": "2026-06-16T17:46:03+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/agents/domain.md", @@ -44,7 +44,7 @@ "openspec/changes/archive", "openspec/specs" ], - "updated_at": "2026-06-16T17:49:38+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/agents/issue-tracker.md", @@ -77,7 +77,7 @@ "scripts/joinquant_tools/FeishuRelayTools.py", "openspec/changes/archive/feishu-relay-tools/design.md" ], - "updated_at": "2026-06-16T17:45:16+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/guides/local-python-env.md", @@ -120,7 +120,7 @@ "scripts/research/platform", "openspec/changes/archive/research-platform-architecture/design.md" ], - "updated_at": "2026-06-16T17:45:26+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_Alpha101.md", @@ -130,7 +130,7 @@ "date": "2018-12-24", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:54+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_Alpha191.md", @@ -140,7 +140,7 @@ "date": "2017-06-16", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:54+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_债券数据.md", @@ -150,7 +150,7 @@ "date": "2016-07-25", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:54+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_场内基金数据.md", @@ -160,7 +160,7 @@ "date": "2009-11-19", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:54+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_场外基金.md", @@ -170,7 +170,7 @@ "date": "2001-12-18", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:57+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_宏观经济数据.md", @@ -180,7 +180,7 @@ "date": null, "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:58+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_常见问题.md", @@ -190,7 +190,7 @@ "date": "2019-12-01", "tags": [], "pathrefs": [], - "updated_at": "2026-05-28T07:57:23+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_技术分析指标.md", @@ -200,7 +200,7 @@ "date": "2019-08-05", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:59+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_指数数据.md", @@ -210,7 +210,7 @@ "date": "2015-10-15", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:59+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_期权数据.md", @@ -220,7 +220,7 @@ "date": "2018-09-25", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:59+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_期货数据.md", @@ -230,7 +230,7 @@ "date": "2003-07-15", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:00+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_聚宽因子库.md", @@ -240,7 +240,7 @@ "date": "2017-01-01", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:00+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_股票数据.md", @@ -250,7 +250,7 @@ "date": "2015-10-10", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:01+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_舆情数据.md", @@ -260,7 +260,7 @@ "date": "2019-02-19", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:01+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_行业概念数据.md", @@ -270,7 +270,7 @@ "date": "2017-05-03", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:02+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/reference/cc-switch-cli.md", @@ -280,7 +280,7 @@ "date": null, "tags": [], "pathrefs": [], - "updated_at": "2026-05-28T07:57:23+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/reference/joinquant-api.md", @@ -380,7 +380,7 @@ "date": null, "tags": [], "pathrefs": [], - "updated_at": "2026-06-16T17:45:07+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/rules/environments.md", @@ -405,7 +405,7 @@ "docs/rules/pr-flow-interface-contract.yaml", "openspec/changes/archive/adr-0010-local-branch-history-rewrite-gate" ], - "updated_at": "2026-06-16T17:44:58+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/rules/index.md", @@ -431,7 +431,7 @@ "docs/rules/governance.md", "openspec/changes/archive" ], - "updated_at": "2026-06-16T17:49:50+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/rules/pr-workflow.md", diff --git a/docs/indexes/reports.json b/docs/indexes/reports.json index aff5bc24..6eaf565e 100644 --- a/docs/indexes/reports.json +++ b/docs/indexes/reports.json @@ -1,8 +1,8 @@ { "schema_version": 1, "catalog_type": "documents", - "generated_at": "2026-06-16T17:54:56+00:00", - "count": 418, + "generated_at": "2026-06-30T21:29:19+00:00", + "count": 430, "reports": [ { "path": "docs/README.md", @@ -28,7 +28,7 @@ "docs/rules/governance.md", "openspec/changes/archive/adr-0007-pr-flow-closed-loop-review-evidence" ], - "updated_at": "2026-06-16T17:46:03+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/agents/domain.md", @@ -44,7 +44,7 @@ "openspec/changes/archive", "openspec/specs" ], - "updated_at": "2026-06-16T17:49:38+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/agents/issue-tracker.md", @@ -77,7 +77,7 @@ "scripts/joinquant_tools/FeishuRelayTools.py", "openspec/changes/archive/feishu-relay-tools/design.md" ], - "updated_at": "2026-06-16T17:45:16+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/guides/local-python-env.md", @@ -120,7 +120,7 @@ "scripts/research/platform", "openspec/changes/archive/research-platform-architecture/design.md" ], - "updated_at": "2026-06-16T17:45:26+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_Alpha101.md", @@ -130,7 +130,7 @@ "date": "2018-12-24", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:54+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_Alpha191.md", @@ -140,7 +140,7 @@ "date": "2017-06-16", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:54+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_债券数据.md", @@ -150,7 +150,7 @@ "date": "2016-07-25", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:54+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_场内基金数据.md", @@ -160,7 +160,7 @@ "date": "2009-11-19", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:54+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_场外基金.md", @@ -170,7 +170,7 @@ "date": "2001-12-18", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:57+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_宏观经济数据.md", @@ -180,7 +180,7 @@ "date": null, "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:58+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_常见问题.md", @@ -190,7 +190,7 @@ "date": "2019-12-01", "tags": [], "pathrefs": [], - "updated_at": "2026-05-28T07:57:23+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_技术分析指标.md", @@ -200,7 +200,7 @@ "date": "2019-08-05", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:59+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_指数数据.md", @@ -210,7 +210,7 @@ "date": "2015-10-15", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:59+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_期权数据.md", @@ -220,7 +220,7 @@ "date": "2018-09-25", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:51:59+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_期货数据.md", @@ -230,7 +230,7 @@ "date": "2003-07-15", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:00+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_聚宽因子库.md", @@ -240,7 +240,7 @@ "date": "2017-01-01", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:00+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_股票数据.md", @@ -250,7 +250,7 @@ "date": "2015-10-10", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:01+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_舆情数据.md", @@ -260,7 +260,7 @@ "date": "2019-02-19", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:01+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/joinquant-api/JQ_行业概念数据.md", @@ -270,7 +270,7 @@ "date": "2017-05-03", "tags": [], "pathrefs": [], - "updated_at": "2026-05-02T13:52:02+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/reference/cc-switch-cli.md", @@ -280,7 +280,7 @@ "date": null, "tags": [], "pathrefs": [], - "updated_at": "2026-05-28T07:57:23+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/reference/joinquant-api.md", @@ -380,7 +380,7 @@ "date": null, "tags": [], "pathrefs": [], - "updated_at": "2026-06-16T17:45:07+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/rules/environments.md", @@ -405,7 +405,7 @@ "docs/rules/pr-flow-interface-contract.yaml", "openspec/changes/archive/adr-0010-local-branch-history-rewrite-gate" ], - "updated_at": "2026-06-16T17:44:58+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/rules/index.md", @@ -431,7 +431,7 @@ "docs/rules/governance.md", "openspec/changes/archive" ], - "updated_at": "2026-06-16T17:49:50+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "docs/rules/pr-workflow.md", @@ -3251,6 +3251,126 @@ "pathrefs": [], "updated_at": "2026-05-28T07:57:25+00:00" }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:14:45+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:14:45+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:27:44+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:27:44+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:29:20+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:29:20+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:38:34+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:38:34+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T18:50:06+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T18:50:06+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T19:37:11+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T19:37:11+00:00" + }, { "path": "strategies/etf_factor_rotation/reports/2026-05-07-factor-vs-dynamic-rebalance-attribution.md", "title": "ETF 因子轮动 vs 动态再平衡深度归因报告", @@ -3377,7 +3497,7 @@ "joinquant_data/JQ_场内基金数据.md", "repo/AGENTS.md" ], - "updated_at": "2026-06-04T17:14:06+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "strategies/etf_factor_rotation/reports/design/2026-05-17-cash-utilization-optimization-plan.md", @@ -3446,7 +3566,7 @@ "joinquant_data/JQ_场内基金数据.md", "strategy_reports(strategy=etf_factor_rotation)/testing/测试方案设计文档.md" ], - "updated_at": "2026-05-28T07:57:25+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "strategies/etf_factor_rotation/reports/design/ETF轮动策略方案说明书.md", @@ -4306,7 +4426,7 @@ "backtest_report_dir(strategy=etf_factor_rotation, run_id=20260508-0027-btb2784ac85d31dd228ec8f7e4e36ce212)/strategy-analysis.md", "backtest_report_dir(strategy=etf_factor_rotation, run_id=20260508-0027-btb2784ac85d31dd228ec8f7e4e36ce212)/performance-analysis.md" ], - "updated_at": "2026-05-28T07:57:26+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "strategies/etf_factor_rotation/test_batches/20260508-hard-ma-scan/report/etf-ma-parameter-attribution.md", @@ -4382,7 +4502,7 @@ "backtest_report_dir(strategy=etf_factor_rotation, run_id=20260508-0027-btb2784ac85d31dd228ec8f7e4e36ce212)/strategy-analysis.md", "backtest_report_dir(strategy=etf_factor_rotation, run_id=20260508-0027-btb2784ac85d31dd228ec8f7e4e36ce212)/performance-analysis.md" ], - "updated_at": "2026-05-28T07:57:26+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "strategies/etf_factor_rotation/test_batches/20260514-etf-ma-mixed-confirmation/report/param-scan-report.md", diff --git a/docs/indexes/reports.md b/docs/indexes/reports.md index 02213aac..df1c48dd 100644 --- a/docs/indexes/reports.md +++ b/docs/indexes/reports.md @@ -298,6 +298,18 @@ | backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260526-2330-btfefcb2db54c231df54d773457629bda1/report/data-integrity.md` | 0 | | backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260526-2333-bt36f34fb52505979aa53d97ef4949fc60/report/backtest_report.md` | 0 | | backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260526-2333-bt36f34fb52505979aa53d97ef4949fc60/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/data-integrity.md` | 0 | | report | etf_factor_rotation | ETF 因子轮动 vs 动态再平衡深度归因报告 | `strategies/etf_factor_rotation/reports/2026-05-07-factor-vs-dynamic-rebalance-attribution.md` | 16 | | report | etf_factor_rotation | 深度归因分析报告 | `strategies/etf_factor_rotation/reports/2026-05-14-deep-attribution.md` | 7 | | report | etf_factor_rotation | 拥挤度惩罚 AB 验证 — 最终决策 | `strategies/etf_factor_rotation/reports/2026-05-15-crowd-penalty-ab-decision.md` | 8 | diff --git a/docs/indexes/reports_catalog.json b/docs/indexes/reports_catalog.json index 35f578e1..ddb6e6d8 100644 --- a/docs/indexes/reports_catalog.json +++ b/docs/indexes/reports_catalog.json @@ -1,8 +1,8 @@ { "schema_version": 1, "catalog_type": "reports", - "generated_at": "2026-06-16T17:54:56+00:00", - "count": 381, + "generated_at": "2026-06-30T21:29:19+00:00", + "count": 393, "reports": [ { "path": "strategies/etf_dynamic_rebalance/backtest_runs/20260504_1/report/attribution-analysis.md", @@ -2761,6 +2761,126 @@ "pathrefs": [], "updated_at": "2026-05-28T07:57:25+00:00" }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:14:45+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:14:45+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:27:44+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:27:44+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:29:20+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:29:20+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:38:34+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T16:38:34+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T18:50:06+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T18:50:06+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/backtest_report.md", + "title": "回测数据汇总", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2021-01-01", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T19:37:11+00:00" + }, + { + "path": "strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/data-integrity.md", + "title": "数据完整性报告", + "category": "backtest_run", + "strategy": "etf_factor_rotation", + "date": "2026-06-30", + "tags": [], + "pathrefs": [], + "updated_at": "2026-06-30T19:37:11+00:00" + }, { "path": "strategies/etf_factor_rotation/reports/2026-05-07-factor-vs-dynamic-rebalance-attribution.md", "title": "ETF 因子轮动 vs 动态再平衡深度归因报告", @@ -2887,7 +3007,7 @@ "joinquant_data/JQ_场内基金数据.md", "repo/AGENTS.md" ], - "updated_at": "2026-06-04T17:14:06+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "strategies/etf_factor_rotation/reports/design/2026-05-17-cash-utilization-optimization-plan.md", @@ -2956,7 +3076,7 @@ "joinquant_data/JQ_场内基金数据.md", "strategy_reports(strategy=etf_factor_rotation)/testing/测试方案设计文档.md" ], - "updated_at": "2026-05-28T07:57:25+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "strategies/etf_factor_rotation/reports/design/ETF轮动策略方案说明书.md", @@ -3816,7 +3936,7 @@ "backtest_report_dir(strategy=etf_factor_rotation, run_id=20260508-0027-btb2784ac85d31dd228ec8f7e4e36ce212)/strategy-analysis.md", "backtest_report_dir(strategy=etf_factor_rotation, run_id=20260508-0027-btb2784ac85d31dd228ec8f7e4e36ce212)/performance-analysis.md" ], - "updated_at": "2026-05-28T07:57:26+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "strategies/etf_factor_rotation/test_batches/20260508-hard-ma-scan/report/etf-ma-parameter-attribution.md", @@ -3892,7 +4012,7 @@ "backtest_report_dir(strategy=etf_factor_rotation, run_id=20260508-0027-btb2784ac85d31dd228ec8f7e4e36ce212)/strategy-analysis.md", "backtest_report_dir(strategy=etf_factor_rotation, run_id=20260508-0027-btb2784ac85d31dd228ec8f7e4e36ce212)/performance-analysis.md" ], - "updated_at": "2026-05-28T07:57:26+00:00" + "updated_at": "2026-06-16T19:45:44+00:00" }, { "path": "strategies/etf_factor_rotation/test_batches/20260514-etf-ma-mixed-confirmation/report/param-scan-report.md", diff --git a/docs/indexes/reports_catalog.md b/docs/indexes/reports_catalog.md index 90e9f48f..66b4a653 100644 --- a/docs/indexes/reports_catalog.md +++ b/docs/indexes/reports_catalog.md @@ -261,6 +261,18 @@ | backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260526-2330-btfefcb2db54c231df54d773457629bda1/report/data-integrity.md` | 0 | | backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260526-2333-bt36f34fb52505979aa53d97ef4949fc60/report/backtest_report.md` | 0 | | backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260526-2333-bt36f34fb52505979aa53d97ef4949fc60/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/data-integrity.md` | 0 | +| backtest_run | etf_factor_rotation | 回测数据汇总 | `strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/backtest_report.md` | 0 | +| backtest_run | etf_factor_rotation | 数据完整性报告 | `strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/data-integrity.md` | 0 | | report | etf_factor_rotation | ETF 因子轮动 vs 动态再平衡深度归因报告 | `strategies/etf_factor_rotation/reports/2026-05-07-factor-vs-dynamic-rebalance-attribution.md` | 16 | | report | etf_factor_rotation | 深度归因分析报告 | `strategies/etf_factor_rotation/reports/2026-05-14-deep-attribution.md` | 7 | | report | etf_factor_rotation | 拥挤度惩罚 AB 验证 — 最终决策 | `strategies/etf_factor_rotation/reports/2026-05-15-crowd-penalty-ab-decision.md` | 8 | diff --git a/docs/indexes/variants_catalog.json b/docs/indexes/variants_catalog.json index f2f52c8e..1fcca1a5 100644 --- a/docs/indexes/variants_catalog.json +++ b/docs/indexes/variants_catalog.json @@ -1,6 +1,37 @@ { "schema_version": 1, - "generated_at": "2026-06-16T17:54:56+00:00", - "count": 0, - "variants": [] + "generated_at": "2026-06-30T21:29:19+00:00", + "count": 3, + "variants": [ + { + "strategy": "etf_factor_rotation", + "variant_id": "baseline_original", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "原始正式基线代码:本次实验对照组,来源为本分支起点 origin/main 的正式策略脚本。", + "updated_at": "2026-06-30T20:21:17+00:00", + "detail": "baseline_original.json" + }, + { + "strategy": "etf_factor_rotation", + "variant_id": "logic3_code_variant", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "旧实盘近似代码变体:周一标记信号,下一交易日开盘生成并执行。", + "updated_at": "2026-06-30T16:23:28+00:00", + "detail": "logic3_code_variant.json" + }, + { + "strategy": "etf_factor_rotation", + "variant_id": "weekend_close_signal_next_open_variant", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "新实盘对齐代码变体:本周最后交易日收盘生成信号,次交易日开盘执行缓存计划。", + "updated_at": "2026-06-30T16:23:28+00:00", + "detail": "weekend_close_signal_next_open_variant.json" + } + ] } \ No newline at end of file diff --git a/research_datasets/catalog.json b/research_datasets/catalog.json index e844bad2..b3454267 100644 --- a/research_datasets/catalog.json +++ b/research_datasets/catalog.json @@ -1312,6 +1312,86 @@ "owner": "research-platform", "lifecycle": "active" }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "fingerprint": "sha256:4eabaa7858e197ea1d9e60e733291c4c5c23b8822ba65db20f3dba4dd9992f11", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "fingerprint": "sha256:2a9db19d253f131c38dff3e91f28fbdd1251f19f1a9fc3c0f11ac0f1333c4b3a", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "fingerprint": "sha256:cd9d4301f3c996c294a5f0b66962a6c053d831ef2a0ddd1914746ad9be1105c5", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "superseded", + "superseded_by": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "superseded_reason": "initial no-trade run; s04 is the active reproducible weekend-close variant evidence" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "fingerprint": "sha256:6c349f1a89a62304482c4289e10433ad831b4ad6ffc42bb3326ced70e785ac96", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "fingerprint": "sha256:cc0f052868017cb487d7811ee5e4de461f0af77b9459546ccc69be8a3c5c82ca", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, + { + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + "fingerprint": "sha256:a3006b9f096754d0b2876f2138ccba09d0a23e63cffc91a6c3214b73361e0bbb", + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "source_kind": "joinquant_backtest_run", + "owner": "research-platform", + "lifecycle": "active" + }, { "dataset_id": "etf_factor_rotation_baseline_audit", "snapshot_id": "2026-05-17T17-36-27Z_56361cf8a32e", diff --git a/research_datasets/catalog.md b/research_datasets/catalog.md index 43ae6b44..13c66fd5 100644 --- a/research_datasets/catalog.md +++ b/research_datasets/catalog.md @@ -103,6 +103,12 @@ | etf_factor_rotation_backtest_runs | 20260526-2327-bta35e64a2599b009eba3c6b8219d61e22 | joinquant_backtest_run | research-platform | active | 1289 | 2021-01-04 ~ 2026-04-30 | | etf_factor_rotation_backtest_runs | 20260526-2330-btfefcb2db54c231df54d773457629bda1 | joinquant_backtest_run | research-platform | active | 1289 | 2021-01-04 ~ 2026-04-30 | | etf_factor_rotation_backtest_runs | 20260526-2333-bt36f34fb52505979aa53d97ef4949fc60 | joinquant_backtest_run | research-platform | active | 1289 | 2021-01-04 ~ 2026-04-30 | +| etf_factor_rotation_backtest_runs | 20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0 | joinquant_backtest_run | research-platform | active | 1289 | 2021-01-04 ~ 2026-04-30 | +| etf_factor_rotation_backtest_runs | 20260701-0025-bt0d4e21c752867aa36b89ee13d7540352 | joinquant_backtest_run | research-platform | active | 1289 | 2021-01-04 ~ 2026-04-30 | +| etf_factor_rotation_backtest_runs | 20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5 | joinquant_backtest_run | research-platform | superseded | 1289 | 2021-01-04 ~ 2026-04-30 | +| etf_factor_rotation_backtest_runs | 20260701-0032-btdcc6252aadef8bb09592538da4dde54c | joinquant_backtest_run | research-platform | active | 1289 | 2021-01-04 ~ 2026-04-30 | +| etf_factor_rotation_backtest_runs | 20260701-0246-bt526c312fb836d6e07826ce170809486a | joinquant_backtest_run | research-platform | active | 1289 | 2021-01-04 ~ 2026-04-30 | +| etf_factor_rotation_backtest_runs | 20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d | joinquant_backtest_run | research-platform | active | 1289 | 2021-01-04 ~ 2026-04-30 | | etf_factor_rotation_baseline_audit | 2026-05-17T17-36-27Z_56361cf8a32e | joinquant_audit_log_jsonl | research-platform | active | 272 | 2021-01-01 ~ 2026-04-30 | | ma_crossover_backtest_runs | 20260505-1939-bt48ef4a644906d1ada69ba04637aa4bb6 | joinquant_backtest_run | research-platform | active | 56 | 2026-01-05 ~ 2026-03-31 | | ma_crossover_backtest_runs | 20260505-1947-bt9d35a958df27c3d1c9534f43644cdd46 | joinquant_backtest_run | research-platform | active | 56 | 2026-01-05 ~ 2026-03-31 | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/README.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/README.md new file mode 100644 index 00000000..25dd9160 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/README.md @@ -0,0 +1,9 @@ +# etf_factor_rotation_backtest_runs + +- **snapshot**: `20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0` +- **run_id**: `20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0` +- **fingerprint**: `sha256:4eabaa7858e197ea1d9e60e733291c4c5c23b8822ba65db20f3dba4dd9992f11` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1090` + +原始文件压缩保存在 `raw/*.gz`;常用轻量索引见 `views/`;程序读取优先使用 `data/data.parquet`。 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/data/audit_events.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/data/audit_events.parquet new file mode 100644 index 00000000..04532692 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/data/audit_events.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:174cac57b6f902f03592c19ef48112bdb9cc6e909ff2a89b9c95dd6d3ceaa69c +size 94149 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/data/data.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/data/data.parquet new file mode 100644 index 00000000..730370e9 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/data/data.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b89820ab491c7834bbb1d07484f7b0118b83da7bc8b371ea4c4a50d62bb42c44 +size 16228 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/dataset.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/dataset.json new file mode 100644 index 00000000..18162007 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/dataset.json @@ -0,0 +1,171 @@ +{ + "schema_version": 1, + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "fingerprint": "sha256:4eabaa7858e197ea1d9e60e733291c4c5c23b8822ba65db20f3dba4dd9992f11", + "created_at": "2026-06-30T16:14:49+00:00", + "owner": "research-platform", + "created_by": "research-platform", + "lifecycle": "active", + "source": { + "kind": "joinquant_backtest_run", + "path": "D:/My Project/Quant Trading/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0" + }, + "storage": { + "canonical": "parquet", + "compression": "zstd", + "raw": "json.gz/jsonl.gz/md.gz" + }, + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "run_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "partial": false, + "missing_required_files": [], + "summary_metrics": { + "策略收益": "127.12%", + "策略年化收益": "17.25%", + "基准收益": "-7.75%", + "超额收益": "146.21%", + "最大回撤": "7.60%", + "最大回撤区间": "2021-11-22,2022-01-28", + "阿尔法": "0.140", + "贝塔": "0.139", + "夏普比率": "1.582", + "索提诺比率": "2.346", + "信息比率": "1.084", + "策略波动率": "0.084", + "基准波动率": "0.179", + "胜率": "0.740", + "盈亏比": "4.966", + "日胜率": "0.542", + "盈利次数": 142, + "亏损次数": 50 + }, + "source_metadata": { + "strategy_name": "etf_factor_rotation", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "8d074a3098c6308f791ea33d8806d6f0", + "backtest_url": "", + "generated_at": "2026-07-01T00:14:34", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_8d074a3098c6308f791ea33d8806d6f0.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "234573daf021757752090358c356f903", + "audit_token": "etf_factor_rotation-s01-baseline-original-20260701001028-3e0b4107", + "audit_path": "jq_auto_audit/etf_factor_rotation-s01-baseline-original-20260701001028-3e0b4107.jsonl" + }, + "audit_line_count": 1090, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "rebalance_order": 816, + "rebalance_signals": 272, + "run_end": 1, + "run_start": 1 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ], + "files": { + "raw": "raw/source.json.gz", + "profile_json": "views/profile.json", + "audit_events": "data/audit_events.parquet", + "daily_returns": "data/data.parquet", + "canonical": "data/data.parquet", + "sample": "views/sample.csv", + "api_export_source": "raw/api_export.json.gz", + "detail_api_export_source": "raw/detail_api_export.json.gz", + "summary_metrics": "raw/summary_metrics.json.gz", + "audit_log_source": "raw/audit_log.jsonl.gz", + "daily_returns_source": "raw/daily_returns.md.gz", + "positioninfo_source": "raw/positioninfo.md.gz", + "transactioninfo_source": "raw/transactioninfo.md.gz", + "balances_source": "raw/balances.md.gz", + "period_risks_source": "raw/period_risks.md.gz", + "logs_source": "raw/logs.md.gz" + }, + "raw_file_integrity": { + "api_export.json": { + "dataset_file": "raw/api_export.json.gz", + "original_sha256": "f7a967d3b67c2c52e049aeeaa3dafc0920f3008f0cd495e2ad5cb33eb4ae4221", + "compressed_sha256": "7c8fa08bc9602a269a706e74c2d6561377418719ac90c7ec2e2572ce59f5af34", + "original_bytes": 3356598 + }, + "detail_api_export.json": { + "dataset_file": "raw/detail_api_export.json.gz", + "original_sha256": "82f066ba6c46bf633ff5487715febaee63804667bac0332d4d7da0d0805df0bf", + "compressed_sha256": "d96601104c203561628d12a21f6c79dc08ff11f2833fb7d3e3783ab25b6f2488", + "original_bytes": 4350900 + }, + "summary_metrics.json": { + "dataset_file": "raw/summary_metrics.json.gz", + "original_sha256": "74e9b525f50b3d66d1fdd881ac3cff7fa27b499260ba211f5392035121d16ea2", + "compressed_sha256": "b57ad5f2a7632af8588e874104c77b85537c143cbd613f9e7c3035081d7764a1", + "original_bytes": 520 + }, + "tabs_raw/audit_log.jsonl": { + "dataset_file": "raw/audit_log.jsonl.gz", + "original_sha256": "4b9c3166531201de33fccec8dce07ad33e80f4f70420cc15bb7aa2c9c56620ff", + "compressed_sha256": "424c1b1d9800c7a7c50d489ca18d8325b34a4bcd6013e8c8c978649c56fa0a3c", + "original_bytes": 1318032 + }, + "tabs_raw/daily_returns.md": { + "dataset_file": "raw/daily_returns.md.gz", + "original_sha256": "73afb8b1c7783773a152041e2d7fd3a96ff1fec6cfc3bc7a8758f63c08073970", + "compressed_sha256": "c4b51782dd103013d6239ea85b91f3892829ad21546b10ffc05cc520457e5578", + "original_bytes": 92875 + }, + "tabs_raw/positioninfo.md": { + "dataset_file": "raw/positioninfo.md.gz", + "original_sha256": "e2f334231893babfb1b91d1076f5f407d541713c23a841a9443216e1a6057e8e", + "compressed_sha256": "3245ae8b76e39d33c4d89e7014de3980458d6da40764819dfb754ee08bc73f74", + "original_bytes": 379897 + }, + "tabs_raw/transactioninfo.md": { + "dataset_file": "raw/transactioninfo.md.gz", + "original_sha256": "ec6ada5cd42a5306b4b1f739dce3be3df953f16ffc40a6262dc3c6579ac1809b", + "compressed_sha256": "a342acd478bf91911ecf7f9258321e5b305c5f5a651bc9dd04863eb104842620", + "original_bytes": 31967 + }, + "tabs_raw/balances.md": { + "dataset_file": "raw/balances.md.gz", + "original_sha256": "5d4c2b9f53deda2fbdd7e5f7c1cd0f8f9e13cc5b3540fea21d12e263a7937d74", + "compressed_sha256": "9bff925058a9f54ab8623d4ec4efa31ba12a76621731f5e532f9e546dbc03be8", + "original_bytes": 90889 + }, + "tabs_raw/period_risks.md": { + "dataset_file": "raw/period_risks.md.gz", + "original_sha256": "00b68a33695b80da0837551ca441973d343584a66a44c0a43844f7412ddc7036", + "compressed_sha256": "90a9e983600498389a278aa856e10118fd99a6fe2e42eb61f046667a8d8afab6", + "original_bytes": 62839 + }, + "tabs_raw/logs.md": { + "dataset_file": "raw/logs.md.gz", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "05269fff42e0b0e739e4003fbb5694a7160ca23692873edd5f4f022b419d0812", + "original_bytes": 290 + } + } +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/raw/source.json.gz b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/raw/source.json.gz new file mode 100644 index 00000000..4e8b7945 Binary files /dev/null and b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/raw/source.json.gz differ diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/profile.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/profile.json new file mode 100644 index 00000000..d48dd153 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/profile.json @@ -0,0 +1,47 @@ +{ + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "audit_line_count": 1090, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "rebalance_order": 816, + "rebalance_signals": 272, + "run_end": 1, + "run_start": 1 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "summary_metric_keys": [ + "亏损次数", + "信息比率", + "基准收益", + "基准波动率", + "夏普比率", + "日胜率", + "最大回撤", + "最大回撤区间", + "盈亏比", + "盈利次数", + "策略年化收益", + "策略收益", + "策略波动率", + "索提诺比率", + "胜率", + "贝塔", + "超额收益", + "阿尔法" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ] +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/profile.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/profile.md new file mode 100644 index 00000000..42d4926b --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/profile.md @@ -0,0 +1,23 @@ +# 回测 run 概览 + +- **行数**: `1289` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1090` +- **审计日期范围**: `2021-01-01 ~ 2026-04-30` +- **ETF 池**: `159819.XSHE, 513100.XSHG, 518880.XSHG` + +## 审计事件分布 + +| 事件类型 | 数量 | +| --- | ---: | +| rebalance_order | 816 | +| rebalance_signals | 272 | +| run_end | 1 | +| run_start | 1 | + +## 报告文件 + +| 报告文件 | +| --- | +| backtest_report.md | +| data-integrity.md | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/sample.csv b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/sample.csv new file mode 100644 index 00000000..1953f096 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/sample.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27d7f60b2727a95d8820ee644e007d7aa6693ba8e6c62c7839a7abbc27ffd047 +size 710 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/schema.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/schema.md new file mode 100644 index 00000000..14dc0788 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/views/schema.md @@ -0,0 +1,10 @@ +# 回测 run 快照字段 + +| 视图 | 说明 | +| --- | --- | +| `raw/source.json.gz` | 原始 run 文件清单与哈希 | +| `raw/*.gz` | 压缩保存的原始回测文件 | +| `data/data.parquet` | 累计收益序列主存储 | +| `data/audit_events.parquet` | 审计事件主存储 | +| `views/sample.csv` | 收益序列小样本 | +| `views/profile.json` | 行数、日期范围、审计日志和报告文件摘要 | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/README.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/README.md new file mode 100644 index 00000000..96681622 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/README.md @@ -0,0 +1,9 @@ +# etf_factor_rotation_backtest_runs + +- **snapshot**: `20260701-0025-bt0d4e21c752867aa36b89ee13d7540352` +- **run_id**: `20260701-0025-bt0d4e21c752867aa36b89ee13d7540352` +- **fingerprint**: `sha256:2a9db19d253f131c38dff3e91f28fbdd1251f19f1a9fc3c0f11ac0f1333c4b3a` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1903` + +原始文件压缩保存在 `raw/*.gz`;常用轻量索引见 `views/`;程序读取优先使用 `data/data.parquet`。 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/data/audit_events.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/data/audit_events.parquet new file mode 100644 index 00000000..099bbf5e --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/data/audit_events.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25b140ab7ebf7943fb941f399044ee79f70f65f277ffaabe37f5ccfea4e104b6 +size 104150 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/data/data.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/data/data.parquet new file mode 100644 index 00000000..3805a770 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/data/data.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d352237471a3c3d1dcd4e56512dfc8b086e5a38350658253567e2e696e4108e +size 16266 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/dataset.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/dataset.json new file mode 100644 index 00000000..1c5d83c6 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/dataset.json @@ -0,0 +1,174 @@ +{ + "schema_version": 1, + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "fingerprint": "sha256:2a9db19d253f131c38dff3e91f28fbdd1251f19f1a9fc3c0f11ac0f1333c4b3a", + "created_at": "2026-06-30T16:27:44+00:00", + "owner": "research-platform", + "created_by": "research-platform", + "lifecycle": "active", + "source": { + "kind": "joinquant_backtest_run", + "path": "D:/My Project/Quant Trading/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352" + }, + "storage": { + "canonical": "parquet", + "compression": "zstd", + "raw": "json.gz/jsonl.gz/md.gz" + }, + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "run_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "partial": false, + "missing_required_files": [], + "summary_metrics": { + "策略收益": "120.85%", + "策略年化收益": "16.61%", + "基准收益": "-7.75%", + "超额收益": "139.41%", + "最大回撤": "11.66%", + "最大回撤区间": "2021-09-06,2022-10-21", + "阿尔法": "0.134", + "贝塔": "0.134", + "夏普比率": "1.543", + "索提诺比率": "2.276", + "信息比率": "1.047", + "策略波动率": "0.082", + "基准波动率": "0.179", + "胜率": "0.706", + "盈亏比": "4.224", + "日胜率": "0.545", + "盈利次数": 132, + "亏损次数": 55 + }, + "source_metadata": { + "strategy_name": "etf_factor_rotation", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "0d4e21c752867aa36b89ee13d7540352", + "backtest_url": "", + "generated_at": "2026-07-01T00:27:32", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_0d4e21c752867aa36b89ee13d7540352.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "d79fe48b94af96dafe698c1361f19fa9", + "audit_token": "etf_factor_rotation-s02-logic3-code-variant-20260701002504-55d17183", + "audit_path": "jq_auto_audit/etf_factor_rotation-s02-logic3-code-variant-20260701002504-55d17183.jsonl" + }, + "audit_line_count": 1903, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "live_like_rebalance_execute": 272, + "live_like_signal_marked": 272, + "live_like_wait": 269, + "rebalance_order": 816, + "rebalance_signals": 272, + "run_end": 1, + "run_start": 1 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ], + "files": { + "raw": "raw/source.json.gz", + "profile_json": "views/profile.json", + "audit_events": "data/audit_events.parquet", + "daily_returns": "data/data.parquet", + "canonical": "data/data.parquet", + "sample": "views/sample.csv", + "api_export_source": "raw/api_export.json.gz", + "detail_api_export_source": "raw/detail_api_export.json.gz", + "summary_metrics": "raw/summary_metrics.json.gz", + "audit_log_source": "raw/audit_log.jsonl.gz", + "daily_returns_source": "raw/daily_returns.md.gz", + "positioninfo_source": "raw/positioninfo.md.gz", + "transactioninfo_source": "raw/transactioninfo.md.gz", + "balances_source": "raw/balances.md.gz", + "period_risks_source": "raw/period_risks.md.gz", + "logs_source": "raw/logs.md.gz" + }, + "raw_file_integrity": { + "api_export.json": { + "dataset_file": "raw/api_export.json.gz", + "original_sha256": "9bbff9ea33c7a3eefad1611590f0a759af491ef9a17819a873b228c6a74b48c3", + "compressed_sha256": "86eeeea86d10eabf501fd49cb8a9853f45547d03ac43242f24bce433ca7c89da", + "original_bytes": 3657616 + }, + "detail_api_export.json": { + "dataset_file": "raw/detail_api_export.json.gz", + "original_sha256": "e312b3f0bcd00a020c98131ff2a1178cc34ce07ca929b366d49f4098931a70da", + "compressed_sha256": "061144d15485e32f095850bc9afc983e604e6a14ef02cfee08625e34ea5d8c5a", + "original_bytes": 4383483 + }, + "summary_metrics.json": { + "dataset_file": "raw/summary_metrics.json.gz", + "original_sha256": "98d1bf471f920ed60c2e5b707c6ee82a302cda99ba5019d128585bfc2b298125", + "compressed_sha256": "77876f051fe717cab7f6fd148ef2ae02be048c0ed4206decdea1bc8e12a2c2c0", + "original_bytes": 521 + }, + "tabs_raw/audit_log.jsonl": { + "dataset_file": "raw/audit_log.jsonl.gz", + "original_sha256": "03b8321df76348480a1607a310b405d2a36e7893369f0c78106c82b85aeddca7", + "compressed_sha256": "caadfa85f8580e5666266e67582a5c70692a7f0fb360d4a7af126647f9c90bca", + "original_bytes": 1573538 + }, + "tabs_raw/daily_returns.md": { + "dataset_file": "raw/daily_returns.md.gz", + "original_sha256": "3fe98f5676b51e9130b70628e7e4825777a354162acd2f36ea57e2c5d67128ce", + "compressed_sha256": "80b833a3f380632b700e3465a6f7152eb2ace77e9be03bd7afcfc9309185c20d", + "original_bytes": 92608 + }, + "tabs_raw/positioninfo.md": { + "dataset_file": "raw/positioninfo.md.gz", + "original_sha256": "a3215e23b1ff949da0b0f11f24322d98f995f097c65ea821ff7ba849d4e7fb68", + "compressed_sha256": "14b9310ec01355c6aa1e012fabfc15a71e0df847de00122774e13ebfd3aad8af", + "original_bytes": 379635 + }, + "tabs_raw/transactioninfo.md": { + "dataset_file": "raw/transactioninfo.md.gz", + "original_sha256": "e782081b37742ef6b6be217827ed0afb4429871c018b0baf58a77329cb30704d", + "compressed_sha256": "37c49aa9b9d47b49651e4e6cdf114c06dbf0be15fe5c8ead933c576b0968944c", + "original_bytes": 31319 + }, + "tabs_raw/balances.md": { + "dataset_file": "raw/balances.md.gz", + "original_sha256": "bb42c190a0c193426c36e3cae105bd27ce891614abf7f9e10f94dcb9a0aa6274", + "compressed_sha256": "eaf4d539a8607ab70f1d5bbab538b693c50d0d0854119e19b1cc42bebda8a378", + "original_bytes": 90295 + }, + "tabs_raw/period_risks.md": { + "dataset_file": "raw/period_risks.md.gz", + "original_sha256": "12b8099715fc56c767744c28dd4a581e2245ba7f789cd44365fcfcaf3b55c7d1", + "compressed_sha256": "90f7def05b8d70d47e07c67810585f126e549cad52a7ede29c767ff3d01c1b3e", + "original_bytes": 62815 + }, + "tabs_raw/logs.md": { + "dataset_file": "raw/logs.md.gz", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "05269fff42e0b0e739e4003fbb5694a7160ca23692873edd5f4f022b419d0812", + "original_bytes": 290 + } + } +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/raw/source.json.gz b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/raw/source.json.gz new file mode 100644 index 00000000..51711e46 Binary files /dev/null and b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/raw/source.json.gz differ diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/profile.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/profile.json new file mode 100644 index 00000000..559d974b --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/profile.json @@ -0,0 +1,50 @@ +{ + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "audit_line_count": 1903, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "live_like_rebalance_execute": 272, + "live_like_signal_marked": 272, + "live_like_wait": 269, + "rebalance_order": 816, + "rebalance_signals": 272, + "run_end": 1, + "run_start": 1 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "summary_metric_keys": [ + "亏损次数", + "信息比率", + "基准收益", + "基准波动率", + "夏普比率", + "日胜率", + "最大回撤", + "最大回撤区间", + "盈亏比", + "盈利次数", + "策略年化收益", + "策略收益", + "策略波动率", + "索提诺比率", + "胜率", + "贝塔", + "超额收益", + "阿尔法" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ] +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/profile.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/profile.md new file mode 100644 index 00000000..f6666756 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/profile.md @@ -0,0 +1,26 @@ +# 回测 run 概览 + +- **行数**: `1289` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1903` +- **审计日期范围**: `2021-01-01 ~ 2026-04-30` +- **ETF 池**: `159819.XSHE, 513100.XSHG, 518880.XSHG` + +## 审计事件分布 + +| 事件类型 | 数量 | +| --- | ---: | +| live_like_rebalance_execute | 272 | +| live_like_signal_marked | 272 | +| live_like_wait | 269 | +| rebalance_order | 816 | +| rebalance_signals | 272 | +| run_end | 1 | +| run_start | 1 | + +## 报告文件 + +| 报告文件 | +| --- | +| backtest_report.md | +| data-integrity.md | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/sample.csv b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/sample.csv new file mode 100644 index 00000000..f7ace3a2 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/sample.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:083c44fe6c9cb739e99abf1ff2f04ab1b69a737ff55e654bba5cbb08e524fa64 +size 670 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/schema.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/schema.md new file mode 100644 index 00000000..14dc0788 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/views/schema.md @@ -0,0 +1,10 @@ +# 回测 run 快照字段 + +| 视图 | 说明 | +| --- | --- | +| `raw/source.json.gz` | 原始 run 文件清单与哈希 | +| `raw/*.gz` | 压缩保存的原始回测文件 | +| `data/data.parquet` | 累计收益序列主存储 | +| `data/audit_events.parquet` | 审计事件主存储 | +| `views/sample.csv` | 收益序列小样本 | +| `views/profile.json` | 行数、日期范围、审计日志和报告文件摘要 | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/README.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/README.md new file mode 100644 index 00000000..1d608f5f --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/README.md @@ -0,0 +1,9 @@ +# etf_factor_rotation_backtest_runs + +- **snapshot**: `20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5` +- **run_id**: `20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5` +- **fingerprint**: `sha256:cd9d4301f3c996c294a5f0b66962a6c053d831ef2a0ddd1914746ad9be1105c5` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1291` + +原始文件压缩保存在 `raw/*.gz`;常用轻量索引见 `views/`;程序读取优先使用 `data/data.parquet`。 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/data/audit_events.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/data/audit_events.parquet new file mode 100644 index 00000000..9796e3ca --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/data/audit_events.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:651b3b49e8612c27643100e50d23a424488460a40cb925c58737dea4152298ee +size 26958 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/data/data.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/data/data.parquet new file mode 100644 index 00000000..72ed321f --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/data/data.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc583e5e32c3c00c52d82954ef0da86ebb868da770d1ab3b4ee7732d72b4e940 +size 5409 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/dataset.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/dataset.json new file mode 100644 index 00000000..62aa82de --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/dataset.json @@ -0,0 +1,172 @@ +{ + "schema_version": 1, + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "fingerprint": "sha256:cd9d4301f3c996c294a5f0b66962a6c053d831ef2a0ddd1914746ad9be1105c5", + "created_at": "2026-06-30T16:29:20+00:00", + "owner": "research-platform", + "created_by": "research-platform", + "lifecycle": "superseded", + "source": { + "kind": "joinquant_backtest_run", + "path": "D:/My Project/Quant Trading/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5" + }, + "storage": { + "canonical": "parquet", + "compression": "zstd", + "raw": "json.gz/jsonl.gz/md.gz" + }, + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "run_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "partial": false, + "missing_required_files": [], + "summary_metrics": { + "策略收益": "0.00%", + "策略年化收益": "0.00%", + "基准收益": "-7.75%", + "超额收益": "8.40%", + "最大回撤": "0.00%", + "最大回撤区间": "2021-01-04,2021-01-04", + "阿尔法": "-0.040", + "贝塔": "0.000", + "夏普比率": "0.000", + "索提诺比率": "0.000", + "信息比率": "0.087", + "策略波动率": "0.000", + "基准波动率": "0.179", + "胜率": "0.000", + "盈亏比": "0.000", + "日胜率": "0.505", + "盈利次数": 0, + "亏损次数": 0 + }, + "source_metadata": { + "strategy_name": "etf_factor_rotation", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "cb35093c739b769b0c7cebb1bbe141b5", + "backtest_url": "", + "generated_at": "2026-07-01T00:29:10", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_cb35093c739b769b0c7cebb1bbe141b5.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "34a1ef4f300c8c4763d455b4dd5fdc1f", + "audit_token": "etf_factor_rotation-s03-weekend-close-signal-next-open-variant-20260701002745-dae632f4", + "audit_path": "jq_auto_audit/etf_factor_rotation-s03-weekend-close-signal-next-open-variant-20260701002745-dae632f4.jsonl" + }, + "audit_line_count": 1291, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "run_end": 1, + "run_start": 1, + "weekend_close_signal_skip": 1289 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ], + "files": { + "raw": "raw/source.json.gz", + "profile_json": "views/profile.json", + "audit_events": "data/audit_events.parquet", + "daily_returns": "data/data.parquet", + "canonical": "data/data.parquet", + "sample": "views/sample.csv", + "api_export_source": "raw/api_export.json.gz", + "detail_api_export_source": "raw/detail_api_export.json.gz", + "summary_metrics": "raw/summary_metrics.json.gz", + "audit_log_source": "raw/audit_log.jsonl.gz", + "daily_returns_source": "raw/daily_returns.md.gz", + "positioninfo_source": "raw/positioninfo.md.gz", + "transactioninfo_source": "raw/transactioninfo.md.gz", + "balances_source": "raw/balances.md.gz", + "period_risks_source": "raw/period_risks.md.gz", + "logs_source": "raw/logs.md.gz" + }, + "raw_file_integrity": { + "api_export.json": { + "dataset_file": "raw/api_export.json.gz", + "original_sha256": "c229ecd0a002776c46a8ac61b45afe338320d96f56256f86fba24ae5d1913675", + "compressed_sha256": "4a0508b9259be6e1c383b046076360cf9d4bb33584736686522a9fdfd22b3117", + "original_bytes": 1195328 + }, + "detail_api_export.json": { + "dataset_file": "raw/detail_api_export.json.gz", + "original_sha256": "cf5dbcd96ca03765831910e39f65b0dd4677bbdc53d0cd81183fab54e1c1c077", + "compressed_sha256": "f30a669cd2bf68d772ffe54597f4bd96cd469b580511c438af482e39ce850dc0", + "original_bytes": 1929553 + }, + "summary_metrics.json": { + "dataset_file": "raw/summary_metrics.json.gz", + "original_sha256": "1441610d84c772df69020a9b419b6a0d93d17925a536a0da73302043f938622c", + "compressed_sha256": "d93b7bc879c5078a26700fa2537df9dccb923afaa938a58b2b2fb65c8c3effc5", + "original_bytes": 513 + }, + "tabs_raw/audit_log.jsonl": { + "dataset_file": "raw/audit_log.jsonl.gz", + "original_sha256": "c2bc4e50e30b7d10d5e4442072d4c941a72ad973444e5f280f79185fdba4bdb7", + "compressed_sha256": "8666e944cd6e4c73d8110c1285e631d5e7f19923bf06479d59ce80097cc82695", + "original_bytes": 457444 + }, + "tabs_raw/daily_returns.md": { + "dataset_file": "raw/daily_returns.md.gz", + "original_sha256": "280364f893898ccb15f694c6d4100181227cc07f7286799990f3490b5a1bce6c", + "compressed_sha256": "047a3b8cd21fbde23a45ed77f444e16f52b31ec64294acf1b81bb6906b429855", + "original_bytes": 71979 + }, + "tabs_raw/positioninfo.md": { + "dataset_file": "raw/positioninfo.md.gz", + "original_sha256": "2e84a9b6856d1079b7ef24a3ddace913452650e6d3ed7f1ebda4c606bb982540", + "compressed_sha256": "d051b390de0ac7ab1028c8ffad73921af3d4920ba4dbe9593b0858a2c6820b7c", + "original_bytes": 46 + }, + "tabs_raw/transactioninfo.md": { + "dataset_file": "raw/transactioninfo.md.gz", + "original_sha256": "aef37d19402efee5cf279ac02e7962f8eacdc8c878fc5ff6265185aaf09e6a8e", + "compressed_sha256": "b4b0eae5867c88eeaabb8404edf7c499cbdadef927d80bc4f8e97afdbbbd821d", + "original_bytes": 37 + }, + "tabs_raw/balances.md": { + "dataset_file": "raw/balances.md.gz", + "original_sha256": "ee7877994ae8bba34c4f473a8486c5735751380e0d895811c34bc3b4c999af0f", + "compressed_sha256": "08cb8867e0c9f219c8a89a87f77d0f79bc997c0e78f0fe9f78fe04ef8db02063", + "original_bytes": 89075 + }, + "tabs_raw/period_risks.md": { + "dataset_file": "raw/period_risks.md.gz", + "original_sha256": "07dcbe34d02a74d975ee511e851dd632d867e5d7bbf78e0273902c5ee6b7a679", + "compressed_sha256": "98f73b5d0a3d9998b7874456af774414660a139bc075a11ec21bbb2dfc8f167c", + "original_bytes": 37103 + }, + "tabs_raw/logs.md": { + "dataset_file": "raw/logs.md.gz", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "05269fff42e0b0e739e4003fbb5694a7160ca23692873edd5f4f022b419d0812", + "original_bytes": 290 + } + }, + "superseded_by": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "superseded_reason": "initial no-trade run; s04 is the active reproducible weekend-close variant evidence" +} diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/raw/source.json.gz b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/raw/source.json.gz new file mode 100644 index 00000000..55290284 Binary files /dev/null and b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/raw/source.json.gz differ diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/profile.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/profile.json new file mode 100644 index 00000000..805aad02 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/profile.json @@ -0,0 +1,46 @@ +{ + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "audit_line_count": 1291, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "run_end": 1, + "run_start": 1, + "weekend_close_signal_skip": 1289 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "summary_metric_keys": [ + "亏损次数", + "信息比率", + "基准收益", + "基准波动率", + "夏普比率", + "日胜率", + "最大回撤", + "最大回撤区间", + "盈亏比", + "盈利次数", + "策略年化收益", + "策略收益", + "策略波动率", + "索提诺比率", + "胜率", + "贝塔", + "超额收益", + "阿尔法" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ] +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/profile.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/profile.md new file mode 100644 index 00000000..0804ab2d --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/profile.md @@ -0,0 +1,22 @@ +# 回测 run 概览 + +- **行数**: `1289` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1291` +- **审计日期范围**: `2021-01-01 ~ 2026-04-30` +- **ETF 池**: `159819.XSHE, 513100.XSHG, 518880.XSHG` + +## 审计事件分布 + +| 事件类型 | 数量 | +| --- | ---: | +| run_end | 1 | +| run_start | 1 | +| weekend_close_signal_skip | 1289 | + +## 报告文件 + +| 报告文件 | +| --- | +| backtest_report.md | +| data-integrity.md | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/sample.csv b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/sample.csv new file mode 100644 index 00000000..56c15740 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/sample.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1285e2d033ccbe84f660fd0d3420ce5294be265174cecff5883ff54c57ca42b6 +size 344 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/schema.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/schema.md new file mode 100644 index 00000000..14dc0788 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/views/schema.md @@ -0,0 +1,10 @@ +# 回测 run 快照字段 + +| 视图 | 说明 | +| --- | --- | +| `raw/source.json.gz` | 原始 run 文件清单与哈希 | +| `raw/*.gz` | 压缩保存的原始回测文件 | +| `data/data.parquet` | 累计收益序列主存储 | +| `data/audit_events.parquet` | 审计事件主存储 | +| `views/sample.csv` | 收益序列小样本 | +| `views/profile.json` | 行数、日期范围、审计日志和报告文件摘要 | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/README.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/README.md new file mode 100644 index 00000000..3b678428 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/README.md @@ -0,0 +1,9 @@ +# etf_factor_rotation_backtest_runs + +- **snapshot**: `20260701-0032-btdcc6252aadef8bb09592538da4dde54c` +- **run_id**: `20260701-0032-btdcc6252aadef8bb09592538da4dde54c` +- **fingerprint**: `sha256:6c349f1a89a62304482c4289e10433ad831b4ad6ffc42bb3326ced70e785ac96` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `4681` + +原始文件压缩保存在 `raw/*.gz`;常用轻量索引见 `views/`;程序读取优先使用 `data/data.parquet`。 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/data/audit_events.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/data/audit_events.parquet new file mode 100644 index 00000000..1617ad44 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/data/audit_events.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78fb1106acce069cfdc0600efa73e05d80f7de3b0f031297382beb9f3a35f031 +size 305822 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/data/data.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/data/data.parquet new file mode 100644 index 00000000..ed72c4c2 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/data/data.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b8a48fdd9a0b6985f78fea8db01d95d5a359d3a033d4f686e7e651ed58b6d80 +size 16123 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/dataset.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/dataset.json new file mode 100644 index 00000000..13f82078 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/dataset.json @@ -0,0 +1,174 @@ +{ + "schema_version": 1, + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "fingerprint": "sha256:6c349f1a89a62304482c4289e10433ad831b4ad6ffc42bb3326ced70e785ac96", + "created_at": "2026-06-30T16:38:35+00:00", + "owner": "research-platform", + "created_by": "research-platform", + "lifecycle": "active", + "source": { + "kind": "joinquant_backtest_run", + "path": "D:/My Project/Quant Trading/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c" + }, + "storage": { + "canonical": "parquet", + "compression": "zstd", + "raw": "json.gz/jsonl.gz/md.gz" + }, + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "run_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "partial": false, + "missing_required_files": [], + "summary_metrics": { + "策略收益": "127.57%", + "策略年化收益": "17.29%", + "基准收益": "-7.75%", + "超额收益": "146.70%", + "最大回撤": "7.58%", + "最大回撤区间": "2021-11-22,2022-01-28", + "阿尔法": "0.141", + "贝塔": "0.141", + "夏普比率": "1.591", + "索提诺比率": "2.352", + "信息比率": "1.089", + "策略波动率": "0.084", + "基准波动率": "0.179", + "胜率": "0.733", + "盈亏比": "5.008", + "日胜率": "0.541", + "盈利次数": 140, + "亏损次数": 51 + }, + "source_metadata": { + "strategy_name": "etf_factor_rotation", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "dcc6252aadef8bb09592538da4dde54c", + "backtest_url": "", + "generated_at": "2026-07-01T00:38:20", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_dcc6252aadef8bb09592538da4dde54c.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "8976d3093b13233b86b7a4c7f33c915e", + "audit_token": "etf_factor_rotation-s04-weekend-close-signal-next-open-variant-v2-20260701003223-e458457b", + "audit_path": "jq_auto_audit/etf_factor_rotation-s04-weekend-close-signal-next-open-variant-v2-20260701003223-e458457b.jsonl" + }, + "audit_line_count": 4681, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "rebalance_order": 813, + "rebalance_signals": 1289, + "run_end": 1, + "run_start": 1, + "weekend_close_execute": 271, + "weekend_close_plan_cached": 1289, + "weekend_close_plan_discarded": 1017 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ], + "files": { + "raw": "raw/source.json.gz", + "profile_json": "views/profile.json", + "audit_events": "data/audit_events.parquet", + "daily_returns": "data/data.parquet", + "canonical": "data/data.parquet", + "sample": "views/sample.csv", + "api_export_source": "raw/api_export.json.gz", + "detail_api_export_source": "raw/detail_api_export.json.gz", + "summary_metrics": "raw/summary_metrics.json.gz", + "audit_log_source": "raw/audit_log.jsonl.gz", + "daily_returns_source": "raw/daily_returns.md.gz", + "positioninfo_source": "raw/positioninfo.md.gz", + "transactioninfo_source": "raw/transactioninfo.md.gz", + "balances_source": "raw/balances.md.gz", + "period_risks_source": "raw/period_risks.md.gz", + "logs_source": "raw/logs.md.gz" + }, + "raw_file_integrity": { + "api_export.json": { + "dataset_file": "raw/api_export.json.gz", + "original_sha256": "2bbf6cebbecd26e21ac1f1006b027866d01999577e29080f3cee09dc20a62478", + "compressed_sha256": "41b6846050158d62ea66c163542735e8d5da246f99f8a47d98dbd53f7eab4a92", + "original_bytes": 8085596 + }, + "detail_api_export.json": { + "dataset_file": "raw/detail_api_export.json.gz", + "original_sha256": "1dab601403be7c65c47edd17a93fde95c08ec7f9217a018c826e6d6f13e3b5e9", + "compressed_sha256": "08da94376bf4d659b4021d34f4fcbbebcde6ec36dbf5f6ba69ad81156d823c6a", + "original_bytes": 4322026 + }, + "summary_metrics.json": { + "dataset_file": "raw/summary_metrics.json.gz", + "original_sha256": "317b30d9a2b2f3f72bb2dc7210bde94ef9e389e7939d8423c2b1f5ee5df3e21b", + "compressed_sha256": "135fb1ab5e2fa8cf0bcebadf03780f0ed8d55c488b02372a3b7ccbd393629cc0", + "original_bytes": 520 + }, + "tabs_raw/audit_log.jsonl": { + "dataset_file": "raw/audit_log.jsonl.gz", + "original_sha256": "ceb62476e6ca432598fe3ea267b3158c921375bbb1150672643fd52ba582753a", + "compressed_sha256": "49e9ee759d2e965c26d7779e4578024476bdc9a6ad171355f56822f868b46a8d", + "original_bytes": 5709740 + }, + "tabs_raw/daily_returns.md": { + "dataset_file": "raw/daily_returns.md.gz", + "original_sha256": "fae7f563d4decde5e0f5e2708b6502e4e712b42cb2ce35e8817779cc40060a50", + "compressed_sha256": "8e31e63bf90b2d042d4f045a290db99eb6f3a0892a34462ee3caea975395f468", + "original_bytes": 92691 + }, + "tabs_raw/positioninfo.md": { + "dataset_file": "raw/positioninfo.md.gz", + "original_sha256": "b66559bf2f878363ba35fa31fa8fcb6e0ad2f6c7249c723448db04164ba99e6b", + "compressed_sha256": "2e8ffe3c2f7a5df3669a203e25084a9e1974f5a5c0a4e6ba18fc817b35ee87da", + "original_bytes": 378767 + }, + "tabs_raw/transactioninfo.md": { + "dataset_file": "raw/transactioninfo.md.gz", + "original_sha256": "9231003f977b453cf45f5064d02865af7d04a533f92034c9407db117c9e27c8d", + "compressed_sha256": "453d227ceed56221df0fdae0b17a53ebd4ddfd766a6f42cac90eb4d2f46cd011", + "original_bytes": 31888 + }, + "tabs_raw/balances.md": { + "dataset_file": "raw/balances.md.gz", + "original_sha256": "7f0d0c1bee6b6572516b623634b2a9d2169fea08f48e8a6aa033984cc1b09872", + "compressed_sha256": "aff79e04917b5b162bf6cd1545dbda15100863f97ce46b5a647459a33804fa59", + "original_bytes": 90521 + }, + "tabs_raw/period_risks.md": { + "dataset_file": "raw/period_risks.md.gz", + "original_sha256": "661b2d392c210b2a0a17c1b5c5bfdf78e23dd67d577f87e008166997053e106c", + "compressed_sha256": "cb4afc7f1a21bf684ce07caa9ddb45476fe0733f6e048c4a5be76cc3fdb923a2", + "original_bytes": 62872 + }, + "tabs_raw/logs.md": { + "dataset_file": "raw/logs.md.gz", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "da2d0526c6e8ab4e76a9654f7b2dbb3cb3dea37e4f30cedb17eac65b7e92368c", + "original_bytes": 290 + } + } +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/raw/source.json.gz b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/raw/source.json.gz new file mode 100644 index 00000000..280c9527 Binary files /dev/null and b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/raw/source.json.gz differ diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/profile.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/profile.json new file mode 100644 index 00000000..78c01895 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/profile.json @@ -0,0 +1,50 @@ +{ + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "audit_line_count": 4681, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "rebalance_order": 813, + "rebalance_signals": 1289, + "run_end": 1, + "run_start": 1, + "weekend_close_execute": 271, + "weekend_close_plan_cached": 1289, + "weekend_close_plan_discarded": 1017 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "summary_metric_keys": [ + "亏损次数", + "信息比率", + "基准收益", + "基准波动率", + "夏普比率", + "日胜率", + "最大回撤", + "最大回撤区间", + "盈亏比", + "盈利次数", + "策略年化收益", + "策略收益", + "策略波动率", + "索提诺比率", + "胜率", + "贝塔", + "超额收益", + "阿尔法" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ] +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/profile.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/profile.md new file mode 100644 index 00000000..e3d22b20 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/profile.md @@ -0,0 +1,26 @@ +# 回测 run 概览 + +- **行数**: `1289` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `4681` +- **审计日期范围**: `2021-01-01 ~ 2026-04-30` +- **ETF 池**: `159819.XSHE, 513100.XSHG, 518880.XSHG` + +## 审计事件分布 + +| 事件类型 | 数量 | +| --- | ---: | +| rebalance_order | 813 | +| rebalance_signals | 1289 | +| run_end | 1 | +| run_start | 1 | +| weekend_close_execute | 271 | +| weekend_close_plan_cached | 1289 | +| weekend_close_plan_discarded | 1017 | + +## 报告文件 + +| 报告文件 | +| --- | +| backtest_report.md | +| data-integrity.md | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/sample.csv b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/sample.csv new file mode 100644 index 00000000..6fcdc0c4 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/sample.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfc99a432f44991880db0203cd037969f52c588ec4751f4c5f3d71b47b54dd81 +size 612 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/schema.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/schema.md new file mode 100644 index 00000000..14dc0788 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/views/schema.md @@ -0,0 +1,10 @@ +# 回测 run 快照字段 + +| 视图 | 说明 | +| --- | --- | +| `raw/source.json.gz` | 原始 run 文件清单与哈希 | +| `raw/*.gz` | 压缩保存的原始回测文件 | +| `data/data.parquet` | 累计收益序列主存储 | +| `data/audit_events.parquet` | 审计事件主存储 | +| `views/sample.csv` | 收益序列小样本 | +| `views/profile.json` | 行数、日期范围、审计日志和报告文件摘要 | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/README.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/README.md new file mode 100644 index 00000000..65a8525b --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/README.md @@ -0,0 +1,9 @@ +# etf_factor_rotation_backtest_runs + +- **snapshot**: `20260701-0246-bt526c312fb836d6e07826ce170809486a` +- **run_id**: `20260701-0246-bt526c312fb836d6e07826ce170809486a` +- **fingerprint**: `sha256:cc0f052868017cb487d7811ee5e4de461f0af77b9459546ccc69be8a3c5c82ca` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1630` + +原始文件压缩保存在 `raw/*.gz`;常用轻量索引见 `views/`;程序读取优先使用 `data/data.parquet`。 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/data/audit_events.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/data/audit_events.parquet new file mode 100644 index 00000000..58f29957 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/data/audit_events.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:960e5fab9e20b0847f6d5c01cfc92f9838ab354b65baf9dba67af4b76ffff058 +size 118224 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/data/data.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/data/data.parquet new file mode 100644 index 00000000..ed72c4c2 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/data/data.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b8a48fdd9a0b6985f78fea8db01d95d5a359d3a033d4f686e7e651ed58b6d80 +size 16123 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/dataset.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/dataset.json new file mode 100644 index 00000000..ac43a564 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/dataset.json @@ -0,0 +1,173 @@ +{ + "schema_version": 1, + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "fingerprint": "sha256:cc0f052868017cb487d7811ee5e4de461f0af77b9459546ccc69be8a3c5c82ca", + "created_at": "2026-06-30T18:50:07+00:00", + "owner": "research-platform", + "created_by": "research-platform", + "lifecycle": "active", + "source": { + "kind": "joinquant_backtest_run", + "path": "D:/My Project/Quant Trading/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a" + }, + "storage": { + "canonical": "parquet", + "compression": "zstd", + "raw": "json.gz/jsonl.gz/md.gz" + }, + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "run_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "partial": false, + "missing_required_files": [], + "summary_metrics": { + "策略收益": "127.57%", + "策略年化收益": "17.29%", + "基准收益": "-7.75%", + "超额收益": "146.70%", + "最大回撤": "7.58%", + "最大回撤区间": "2021-11-22,2022-01-28", + "阿尔法": "0.141", + "贝塔": "0.141", + "夏普比率": "1.591", + "索提诺比率": "2.352", + "信息比率": "1.089", + "策略波动率": "0.084", + "基准波动率": "0.179", + "胜率": "0.733", + "盈亏比": "5.008", + "日胜率": "0.541", + "盈利次数": 140, + "亏损次数": 51 + }, + "source_metadata": { + "strategy_name": "etf_factor_rotation", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "526c312fb836d6e07826ce170809486a", + "backtest_url": "", + "generated_at": "2026-07-01T02:49:55", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_526c312fb836d6e07826ce170809486a.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "b6cb7f66e1fd859bcf372f0813ab5d75", + "audit_token": "etf_factor_rotation-s04-weekend-close-signal-next-open-variant-v2-20260701024558-124bb940", + "audit_path": "jq_auto_audit/etf_factor_rotation-s04-weekend-close-signal-next-open-variant-v2-20260701024558-124bb940.jsonl" + }, + "audit_line_count": 1630, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "rebalance_order": 813, + "rebalance_signals": 272, + "run_end": 1, + "run_start": 1, + "weekend_close_execute": 271, + "weekend_close_plan_cached": 272 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ], + "files": { + "raw": "raw/source.json.gz", + "profile_json": "views/profile.json", + "audit_events": "data/audit_events.parquet", + "daily_returns": "data/data.parquet", + "canonical": "data/data.parquet", + "sample": "views/sample.csv", + "api_export_source": "raw/api_export.json.gz", + "detail_api_export_source": "raw/detail_api_export.json.gz", + "summary_metrics": "raw/summary_metrics.json.gz", + "audit_log_source": "raw/audit_log.jsonl.gz", + "daily_returns_source": "raw/daily_returns.md.gz", + "positioninfo_source": "raw/positioninfo.md.gz", + "transactioninfo_source": "raw/transactioninfo.md.gz", + "balances_source": "raw/balances.md.gz", + "period_risks_source": "raw/period_risks.md.gz", + "logs_source": "raw/logs.md.gz" + }, + "raw_file_integrity": { + "api_export.json": { + "dataset_file": "raw/api_export.json.gz", + "original_sha256": "5ca0d57eddc4d68c0666a648fa8ae947e7e245376b79a0bbe27889859a46166b", + "compressed_sha256": "0f220177b98c9168e238171dcd67d2fa097cf0246d03b49f5c890e88bc261704", + "original_bytes": 3827821 + }, + "detail_api_export.json": { + "dataset_file": "raw/detail_api_export.json.gz", + "original_sha256": "12c70ee14b6d0ae1b37613abb337844620a44a3ca3261e301f7f2de6a54b1eff", + "compressed_sha256": "7fc77f963819736f60d350045885ab71908ed41be5a0a9c44e00ef4be7d74905", + "original_bytes": 4370080 + }, + "summary_metrics.json": { + "dataset_file": "raw/summary_metrics.json.gz", + "original_sha256": "317b30d9a2b2f3f72bb2dc7210bde94ef9e389e7939d8423c2b1f5ee5df3e21b", + "compressed_sha256": "135fb1ab5e2fa8cf0bcebadf03780f0ed8d55c488b02372a3b7ccbd393629cc0", + "original_bytes": 520 + }, + "tabs_raw/audit_log.jsonl": { + "dataset_file": "raw/audit_log.jsonl.gz", + "original_sha256": "b673ad3c49c5782fee85e5d062b55ff081a029eb3f58dda4d87ac2dc2b28525b", + "compressed_sha256": "2dd4f6c4a4b5c439e14dd8d30560557c2788de087cd8b7864ad0f22b03b9210c", + "original_bytes": 1742407 + }, + "tabs_raw/daily_returns.md": { + "dataset_file": "raw/daily_returns.md.gz", + "original_sha256": "fae7f563d4decde5e0f5e2708b6502e4e712b42cb2ce35e8817779cc40060a50", + "compressed_sha256": "8e31e63bf90b2d042d4f045a290db99eb6f3a0892a34462ee3caea975395f468", + "original_bytes": 92691 + }, + "tabs_raw/positioninfo.md": { + "dataset_file": "raw/positioninfo.md.gz", + "original_sha256": "b66559bf2f878363ba35fa31fa8fcb6e0ad2f6c7249c723448db04164ba99e6b", + "compressed_sha256": "2e8ffe3c2f7a5df3669a203e25084a9e1974f5a5c0a4e6ba18fc817b35ee87da", + "original_bytes": 378767 + }, + "tabs_raw/transactioninfo.md": { + "dataset_file": "raw/transactioninfo.md.gz", + "original_sha256": "9231003f977b453cf45f5064d02865af7d04a533f92034c9407db117c9e27c8d", + "compressed_sha256": "453d227ceed56221df0fdae0b17a53ebd4ddfd766a6f42cac90eb4d2f46cd011", + "original_bytes": 31888 + }, + "tabs_raw/balances.md": { + "dataset_file": "raw/balances.md.gz", + "original_sha256": "7f0d0c1bee6b6572516b623634b2a9d2169fea08f48e8a6aa033984cc1b09872", + "compressed_sha256": "aff79e04917b5b162bf6cd1545dbda15100863f97ce46b5a647459a33804fa59", + "original_bytes": 90521 + }, + "tabs_raw/period_risks.md": { + "dataset_file": "raw/period_risks.md.gz", + "original_sha256": "661b2d392c210b2a0a17c1b5c5bfdf78e23dd67d577f87e008166997053e106c", + "compressed_sha256": "cb4afc7f1a21bf684ce07caa9ddb45476fe0733f6e048c4a5be76cc3fdb923a2", + "original_bytes": 62872 + }, + "tabs_raw/logs.md": { + "dataset_file": "raw/logs.md.gz", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "da2d0526c6e8ab4e76a9654f7b2dbb3cb3dea37e4f30cedb17eac65b7e92368c", + "original_bytes": 290 + } + } +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/raw/source.json.gz b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/raw/source.json.gz new file mode 100644 index 00000000..46147cf3 Binary files /dev/null and b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/raw/source.json.gz differ diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/profile.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/profile.json new file mode 100644 index 00000000..d206e0e5 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/profile.json @@ -0,0 +1,49 @@ +{ + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "audit_line_count": 1630, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "rebalance_order": 813, + "rebalance_signals": 272, + "run_end": 1, + "run_start": 1, + "weekend_close_execute": 271, + "weekend_close_plan_cached": 272 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "summary_metric_keys": [ + "亏损次数", + "信息比率", + "基准收益", + "基准波动率", + "夏普比率", + "日胜率", + "最大回撤", + "最大回撤区间", + "盈亏比", + "盈利次数", + "策略年化收益", + "策略收益", + "策略波动率", + "索提诺比率", + "胜率", + "贝塔", + "超额收益", + "阿尔法" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ] +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/profile.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/profile.md new file mode 100644 index 00000000..dfd80920 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/profile.md @@ -0,0 +1,25 @@ +# 回测 run 概览 + +- **行数**: `1289` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1630` +- **审计日期范围**: `2021-01-01 ~ 2026-04-30` +- **ETF 池**: `159819.XSHE, 513100.XSHG, 518880.XSHG` + +## 审计事件分布 + +| 事件类型 | 数量 | +| --- | ---: | +| rebalance_order | 813 | +| rebalance_signals | 272 | +| run_end | 1 | +| run_start | 1 | +| weekend_close_execute | 271 | +| weekend_close_plan_cached | 272 | + +## 报告文件 + +| 报告文件 | +| --- | +| backtest_report.md | +| data-integrity.md | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/sample.csv b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/sample.csv new file mode 100644 index 00000000..6fcdc0c4 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/sample.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfc99a432f44991880db0203cd037969f52c588ec4751f4c5f3d71b47b54dd81 +size 612 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/schema.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/schema.md new file mode 100644 index 00000000..14dc0788 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/views/schema.md @@ -0,0 +1,10 @@ +# 回测 run 快照字段 + +| 视图 | 说明 | +| --- | --- | +| `raw/source.json.gz` | 原始 run 文件清单与哈希 | +| `raw/*.gz` | 压缩保存的原始回测文件 | +| `data/data.parquet` | 累计收益序列主存储 | +| `data/audit_events.parquet` | 审计事件主存储 | +| `views/sample.csv` | 收益序列小样本 | +| `views/profile.json` | 行数、日期范围、审计日志和报告文件摘要 | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/README.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/README.md new file mode 100644 index 00000000..05c4647c --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/README.md @@ -0,0 +1,17 @@ +# etf_factor_rotation_backtest_runs + + + +- **snapshot**: `20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d` + +- **run_id**: `20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d` + +- **fingerprint**: `sha256:a3006b9f096754d0b2876f2138ccba09d0a23e63cffc91a6c3214b73361e0bbb` + +- **日期范围**: `2021-01-04 ~ 2026-04-30` + +- **审计日志行数**: `1630` + + + +原始文件压缩保存在 `raw/*.gz`;常用轻量索引见 `views/`;程序读取优先使用 `data/data.parquet`。 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/data/audit_events.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/data/audit_events.parquet new file mode 100644 index 00000000..fea6dd6a --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/data/audit_events.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a0f53d075da65c3b811cd0a3f39d7ecca119fb5333a49ab71bb2ce62108af52 +size 117948 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/data/data.parquet b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/data/data.parquet new file mode 100644 index 00000000..ed72c4c2 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/data/data.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b8a48fdd9a0b6985f78fea8db01d95d5a359d3a033d4f686e7e651ed58b6d80 +size 16123 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/dataset.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/dataset.json new file mode 100644 index 00000000..e14c677b --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/dataset.json @@ -0,0 +1,345 @@ +{ + + "schema_version": 1, + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "fingerprint": "sha256:a3006b9f096754d0b2876f2138ccba09d0a23e63cffc91a6c3214b73361e0bbb", + + "created_at": "2026-06-30T19:37:13+00:00", + + "owner": "research-platform", + + "created_by": "research-platform", + + "lifecycle": "active", + + "source": { + + "kind": "joinquant_backtest_run", + + "path": "D:/My Project/Quant Trading/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d" + + }, + + "storage": { + + "canonical": "parquet", + + "compression": "zstd", + + "raw": "json.gz/jsonl.gz/md.gz" + + }, + + "row_count": 1289, + + "date_range": [ + + "2021-01-04", + + "2026-04-30" + + ], + + "run_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "partial": false, + + "missing_required_files": [], + + "summary_metrics": { + + "策略收益": "127.57%", + + "策略年化收益": "17.29%", + + "基准收益": "-7.75%", + + "超额收益": "146.70%", + + "最大回撤": "7.58%", + + "最大回撤区间": "2021-11-22,2022-01-28", + + "阿尔法": "0.141", + + "贝塔": "0.141", + + "夏普比率": "1.591", + + "索提诺比率": "2.352", + + "信息比率": "1.089", + + "策略波动率": "0.084", + + "基准波动率": "0.179", + + "胜率": "0.733", + + "盈亏比": "5.008", + + "日胜率": "0.541", + + "盈利次数": 140, + + "亏损次数": 51 + + }, + + "source_metadata": { + + "strategy_name": "etf_factor_rotation", + + "strategy_file": "", + + "strategy_dir": "", + + "start_date_requested": "", + + "start_date_effective": "2021-01-01", + + "end_date_requested": "", + + "end_date_effective": "2026-04-30", + + "capital": 100000, + + "backtest_id": "bfc5066102fc7eac2f3dfd3da0276a3d", + + "backtest_url": "", + + "generated_at": "2026-07-01T03:36:53", + + "extraction_method": "joinquant_research_get_backtest", + + "primary_extraction_method": "joinquant_research_get_backtest", + + "fallback_extraction_method": "", + + "research_export_path": "jq_auto_exports/research_backtest_bfc5066102fc7eac2f3dfd3da0276a3d.json", + + "research_downloaded": true, + + "detail_api_used": true, + + "frequency": "1d", + + "py_version": "Python3", + + "internal_backtest_id": "9f29ab61c433f227af01e2ed1cdcff85", + + "audit_token": "etf_factor_rotation-s05-official-current-baseline-20260701033128-41374eef", + + "audit_path": "jq_auto_audit/etf_factor_rotation-s05-official-current-baseline-20260701033128-41374eef.jsonl" + + }, + + "audit_line_count": 1630, + + "audit_date_range": [ + + "2021-01-01", + + "2026-04-30" + + ], + + "audit_event_counts": { + + "rebalance_order": 813, + + "rebalance_signals": 272, + + "run_end": 1, + + "run_start": 1, + + "weekend_close_execute": 271, + + "weekend_close_plan_cached": 272 + + }, + + "etf_pool": [ + + "159819.XSHE", + + "513100.XSHG", + + "518880.XSHG" + + ], + + "report_files": [ + + "backtest_report.md", + + "data-integrity.md" + + ], + + "files": { + + "raw": "raw/source.json.gz", + + "profile_json": "views/profile.json", + + "audit_events": "data/audit_events.parquet", + + "daily_returns": "data/data.parquet", + + "canonical": "data/data.parquet", + + "sample": "views/sample.csv", + + "api_export_source": "raw/api_export.json.gz", + + "detail_api_export_source": "raw/detail_api_export.json.gz", + + "summary_metrics": "raw/summary_metrics.json.gz", + + "audit_log_source": "raw/audit_log.jsonl.gz", + + "daily_returns_source": "raw/daily_returns.md.gz", + + "positioninfo_source": "raw/positioninfo.md.gz", + + "transactioninfo_source": "raw/transactioninfo.md.gz", + + "balances_source": "raw/balances.md.gz", + + "period_risks_source": "raw/period_risks.md.gz", + + "logs_source": "raw/logs.md.gz" + + }, + + "raw_file_integrity": { + + "api_export.json": { + + "dataset_file": "raw/api_export.json.gz", + + "original_sha256": "7c347eb465d800422c4f50a761c401156985f1330b3cb90d585b1bd697a89221", + + "compressed_sha256": "ed009f3dcbe7d329e092fa016fe83dc64c3754d1331fc399e6424e059c1f9726", + + "original_bytes": 3784618 + + }, + + "detail_api_export.json": { + + "dataset_file": "raw/detail_api_export.json.gz", + + "original_sha256": "3ccd53bb53f1e860ba5842f364cc4fec65245e29d8a0f04062d8023fb21047f4", + + "compressed_sha256": "333d4d723204b27a3588141bee9d6b8bf12bc17f2d71a2e855c6d3443d2791b1", + + "original_bytes": 4370413 + + }, + + "summary_metrics.json": { + + "dataset_file": "raw/summary_metrics.json.gz", + + "original_sha256": "317b30d9a2b2f3f72bb2dc7210bde94ef9e389e7939d8423c2b1f5ee5df3e21b", + + "compressed_sha256": "135fb1ab5e2fa8cf0bcebadf03780f0ed8d55c488b02372a3b7ccbd393629cc0", + + "original_bytes": 520 + + }, + + "tabs_raw/audit_log.jsonl": { + + "dataset_file": "raw/audit_log.jsonl.gz", + + "original_sha256": "590310655c88b251a25813ac6ddcd0dd7d318bb88f3a5665a4cf61ca40757e51", + + "compressed_sha256": "8f264094a0c5c4b0c9fa2028fad6c1b77c97cb2cbad15e0880f52b32e83d94ac", + + "original_bytes": 1698903 + + }, + + "tabs_raw/daily_returns.md": { + + "dataset_file": "raw/daily_returns.md.gz", + + "original_sha256": "fae7f563d4decde5e0f5e2708b6502e4e712b42cb2ce35e8817779cc40060a50", + + "compressed_sha256": "8e31e63bf90b2d042d4f045a290db99eb6f3a0892a34462ee3caea975395f468", + + "original_bytes": 92691 + + }, + + "tabs_raw/positioninfo.md": { + + "dataset_file": "raw/positioninfo.md.gz", + + "original_sha256": "b66559bf2f878363ba35fa31fa8fcb6e0ad2f6c7249c723448db04164ba99e6b", + + "compressed_sha256": "2e8ffe3c2f7a5df3669a203e25084a9e1974f5a5c0a4e6ba18fc817b35ee87da", + + "original_bytes": 378767 + + }, + + "tabs_raw/transactioninfo.md": { + + "dataset_file": "raw/transactioninfo.md.gz", + + "original_sha256": "9231003f977b453cf45f5064d02865af7d04a533f92034c9407db117c9e27c8d", + + "compressed_sha256": "5eea156208babdd4921f3702c17e435d5b16c015a4e14341cfc546293f948b86", + + "original_bytes": 31888 + + }, + + "tabs_raw/balances.md": { + + "dataset_file": "raw/balances.md.gz", + + "original_sha256": "7f0d0c1bee6b6572516b623634b2a9d2169fea08f48e8a6aa033984cc1b09872", + + "compressed_sha256": "a56c0800aceb2f51a891f68cd72f8c15229adebf668d89e8d51ef19b9f3d7ad6", + + "original_bytes": 90521 + + }, + + "tabs_raw/period_risks.md": { + + "dataset_file": "raw/period_risks.md.gz", + + "original_sha256": "661b2d392c210b2a0a17c1b5c5bfdf78e23dd67d577f87e008166997053e106c", + + "compressed_sha256": "ef3869e5e1cab71c32cb054dbd7d8decb3db98a6eb1c186d32983f8655ec4be4", + + "original_bytes": 62872 + + }, + + "tabs_raw/logs.md": { + + "dataset_file": "raw/logs.md.gz", + + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + + "compressed_sha256": "f01427d06d60706892b923f8c27fba870fa5891fe71b9523b5c79748da6ee971", + + "original_bytes": 290 + + } + + } + +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/raw/source.json.gz b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/raw/source.json.gz new file mode 100644 index 00000000..891241d1 Binary files /dev/null and b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/raw/source.json.gz differ diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/profile.json b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/profile.json new file mode 100644 index 00000000..d206e0e5 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/profile.json @@ -0,0 +1,49 @@ +{ + "row_count": 1289, + "date_range": [ + "2021-01-04", + "2026-04-30" + ], + "audit_line_count": 1630, + "audit_date_range": [ + "2021-01-01", + "2026-04-30" + ], + "audit_event_counts": { + "rebalance_order": 813, + "rebalance_signals": 272, + "run_end": 1, + "run_start": 1, + "weekend_close_execute": 271, + "weekend_close_plan_cached": 272 + }, + "etf_pool": [ + "159819.XSHE", + "513100.XSHG", + "518880.XSHG" + ], + "summary_metric_keys": [ + "亏损次数", + "信息比率", + "基准收益", + "基准波动率", + "夏普比率", + "日胜率", + "最大回撤", + "最大回撤区间", + "盈亏比", + "盈利次数", + "策略年化收益", + "策略收益", + "策略波动率", + "索提诺比率", + "胜率", + "贝塔", + "超额收益", + "阿尔法" + ], + "report_files": [ + "backtest_report.md", + "data-integrity.md" + ] +} \ No newline at end of file diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/profile.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/profile.md new file mode 100644 index 00000000..dfd80920 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/profile.md @@ -0,0 +1,25 @@ +# 回测 run 概览 + +- **行数**: `1289` +- **日期范围**: `2021-01-04 ~ 2026-04-30` +- **审计日志行数**: `1630` +- **审计日期范围**: `2021-01-01 ~ 2026-04-30` +- **ETF 池**: `159819.XSHE, 513100.XSHG, 518880.XSHG` + +## 审计事件分布 + +| 事件类型 | 数量 | +| --- | ---: | +| rebalance_order | 813 | +| rebalance_signals | 272 | +| run_end | 1 | +| run_start | 1 | +| weekend_close_execute | 271 | +| weekend_close_plan_cached | 272 | + +## 报告文件 + +| 报告文件 | +| --- | +| backtest_report.md | +| data-integrity.md | diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/sample.csv b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/sample.csv new file mode 100644 index 00000000..6fcdc0c4 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/sample.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfc99a432f44991880db0203cd037969f52c588ec4751f4c5f3d71b47b54dd81 +size 612 diff --git a/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/schema.md b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/schema.md new file mode 100644 index 00000000..14dc0788 --- /dev/null +++ b/research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/views/schema.md @@ -0,0 +1,10 @@ +# 回测 run 快照字段 + +| 视图 | 说明 | +| --- | --- | +| `raw/source.json.gz` | 原始 run 文件清单与哈希 | +| `raw/*.gz` | 压缩保存的原始回测文件 | +| `data/data.parquet` | 累计收益序列主存储 | +| `data/audit_events.parquet` | 审计事件主存储 | +| `views/sample.csv` | 收益序列小样本 | +| `views/profile.json` | 行数、日期范围、审计日志和报告文件摘要 | diff --git a/scripts/joinquant_tools/FeishuRelayTools.py b/scripts/joinquant_tools/FeishuRelayTools.py index c4c003a8..7a392970 100644 --- a/scripts/joinquant_tools/FeishuRelayTools.py +++ b/scripts/joinquant_tools/FeishuRelayTools.py @@ -47,6 +47,7 @@ } _print = print +_execution_notice_suppression = None def _log(message): @@ -318,6 +319,97 @@ def _format_order_summary(summary): ) +def _as_list(value): + if value is None: + return [] + try: + if hasattr(value, "tolist"): + value = value.tolist() + except Exception: + pass + if isinstance(value, (list, tuple)): + return list(value) + return [value] + + +def _plan_order_summaries(plan): + pool_source = plan.get("pool") + if pool_source is None: + pool_source = plan.get("etf_pool") + weights_source = plan.get("target_weights") + if weights_source is None: + weights_source = plan.get("final_weights") + pool = _as_list(pool_source) + weights = _as_list(weights_source) + values = _as_list(plan.get("target_values")) + params = plan.get("params") if isinstance(plan.get("params"), dict) else {} + names = _as_list(plan.get("etf_names") or params.get("etf_names")) + total_value = _to_float(plan.get("portfolio_total_value")) + signal_time = str(plan.get("signal_date") or plan.get("asof_date") or _format_time(None)) + run_type = plan.get("run_type") or params.get("run_type") + summaries = [] + for index, security in enumerate(pool): + target_weight = weights[index] if index < len(weights) else None + target_value = values[index] if index < len(values) else None + if target_value in (None, "") and total_value is not None: + weight_float = _to_float(target_weight) + if weight_float is not None: + target_value = total_value * weight_float + name = names[index] if index < len(names) else _get_security_name(security) + summaries.append({ + "time": signal_time, + "action": "信号", + "name": name, + "security": str(security), + "amount": "--", + "price": "--", + "signed_amount": "--", + "trade_value": target_value, + "target_weight": target_weight, + "run_type": run_type, + "strategy": CURRENT_STRATEGY_NAME, + }) + return summaries + + +def report_signal_plan(plan): + if not isinstance(plan, dict): + _log("信号通知失败: plan 不是 dict") + return False + orders = _plan_order_summaries(plan) + if not orders: + _log("信号通知失败: plan 无标的") + return False + signal_date = plan.get("signal_date") or plan.get("asof_date") or "--" + trade_date = plan.get("trade_date") or "next_open" + lines = [ + "信号日期:%s" % signal_date, + "计划交易日:%s" % trade_date, + ] + lines.extend(_format_order_summary(item) for item in orders) + run_type = _resolve_items_run_type(orders) + message = _build_message(CURRENT_STRATEGY_NAME, lines, SECURITY_KEYWORD, run_type=run_type) + batch_id = plan.get("batch_id") or _make_batch_id(CURRENT_STRATEGY_NAME, signal_date, lines) + try: + return bool(_sender.send(message, batch_id=batch_id, orders=orders)) + except Exception as exc: + _log("信号通知发送异常: %s" % _safe_error_text(exc)) + return False + + +def suppress_execution_notice(batch_id=None, reason="signal_notice_already_sent"): + global _execution_notice_suppression + _execution_notice_suppression = { + "batch_id": batch_id, + "reason": reason, + } + + +def resume_execution_notice(): + global _execution_notice_suppression + _execution_notice_suppression = None + + def _build_message(strategy_name, lines, security_keyword="", run_type=None): title = "【%s】飞书交易通知" % strategy_name if security_keyword and security_keyword not in title: @@ -678,6 +770,8 @@ def _install_wrappers(report_func): def _report_order(order_obj, call_context=None): + if _execution_notice_suppression is not None: + return try: summary = _summarize_order(order_obj, CURRENT_STRATEGY_NAME, call_context=call_context) _buffer.add(summary) diff --git a/scripts/joinquant_tools/tests/test_feishu_relay_tools.py b/scripts/joinquant_tools/tests/test_feishu_relay_tools.py index 12b344e5..85563ea0 100644 --- a/scripts/joinquant_tools/tests/test_feishu_relay_tools.py +++ b/scripts/joinquant_tools/tests/test_feishu_relay_tools.py @@ -213,6 +213,64 @@ def place_order(context): assert reported[0]["target_weight"] == 0.2476 +def test_report_signal_plan_sends_signal_notice_with_existing_sender(monkeypatch): + module = load_module(monkeypatch) + sent = [] + monkeypatch.setattr( + module._sender, + "send", + lambda message, batch_id=None, orders=None: sent.append((message, batch_id, orders)) or True, + ) + + result = module.report_signal_plan({ + "batch_id": "signal-batch-1", + "signal_date": "2026-05-15", + "trade_date": "next_open", + "pool": ["513100.XSHG", "518880.XSHG"], + "target_weights": [0.2476, 0.0], + "target_values": [247600.0, 0.0], + "params": {"ExecutionTimingMode": "weekend-close-signal-next-open"}, + }) + + assert result is True + message, batch_id, orders = sent[0] + assert batch_id == "signal-batch-1" + assert "信号日期:2026-05-15" in message + assert "计划交易日:next_open" in message + assert "513100.XSHG" in message + assert orders[0]["action"] == "信号" + assert orders[0]["target_weight"] == 0.2476 + assert orders[0]["trade_value"] == 247600.0 + + +def test_report_signal_plan_returns_false_when_notice_not_sent(monkeypatch): + module = load_module(monkeypatch) + monkeypatch.setattr(module._sender, "send", lambda message, batch_id=None, orders=None: False) + + result = module.report_signal_plan({ + "batch_id": "signal-batch-1", + "signal_date": "2026-05-15", + "pool": ["513100.XSHG"], + "target_weights": [0.2476], + }) + + assert result is False + + +def test_suppressed_execution_notice_skips_order_buffer(monkeypatch): + module = load_module(monkeypatch) + buffered = [] + monkeypatch.setattr(module._buffer, "add", lambda summary: buffered.append(summary)) + + module.suppress_execution_notice(batch_id="signal-batch-1", reason="signal_notice_already_sent") + module._report_order(FakeOrder()) + module.resume_execution_notice() + module._report_order(FakeOrder()) + + assert len(buffered) == 1 + assert buffered[0]["security"] == "513100.XSHG" + + def test_wrapped_order_captures_run_type_from_caller_context(monkeypatch): module = load_module(monkeypatch) reported = [] diff --git a/scripts/tools/jq_automation/browser.py b/scripts/tools/jq_automation/browser.py index 948fad3e..514e7008 100644 --- a/scripts/tools/jq_automation/browser.py +++ b/scripts/tools/jq_automation/browser.py @@ -166,7 +166,7 @@ async def click_compile(self) -> None: continue raise AutomationError("Could not find the JoinQuant compile button") - async def wait_compile_complete(self, timeout_ms: int = 120_000, poll_ms: int = 500) -> dict[str, Any]: + async def wait_compile_complete(self, timeout_ms: int = 300_000, poll_ms: int = 500) -> dict[str, Any]: return await wait_for_compile_completion( self._require_page(), lambda name: self.snippet_reader(name), @@ -485,7 +485,7 @@ async def wait_for_compile_completion( page: Any, snippet_reader: Callable[[str], str], *, - timeout_ms: int = 120_000, + timeout_ms: int = 300_000, poll_ms: int = 500, ) -> dict[str, Any]: source = snippet_reader("compile.js") diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/all_data.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/all_data.json new file mode 100644 index 00000000..54d457ea --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/all_data.json @@ -0,0 +1,105 @@ +{ + "api_export_json": "D:\\My Project\\Quant Trading\\strategies\\etf_factor_rotation\\backtest_runs\\20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0\\api_export.json", + "extraction_method": "research_bundle", + "generated_at": "2026-06-30T16:14:45.885507+00:00", + "counts": { + "results": 1289, + "positions": 2391, + "orders": 379, + "records": 0, + "balances": 1289, + "period_risk_tabs": 10, + "audit_log_lines": 1090 + }, + "partial": { + "results": false, + "positions": false, + "orders": false, + "records": false, + "risk": false, + "period_risks": false, + "balances": false, + "logs": true, + "audit_log": false + }, + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_8d074a3098c6308f791ea33d8806d6f0.json", + "research_downloaded": true, + "detail_api_used": true, + "tabs": { + "daily_returns": { + "path": "tabs_raw/daily_returns.md", + "partial": false, + "record_count": 1289 + }, + "transactioninfo": { + "path": "tabs_raw/transactioninfo.md", + "partial": false, + "record_count": 379 + }, + "positioninfo": { + "path": "tabs_raw/positioninfo.md", + "partial": false, + "record_count": 2391 + }, + "records": { + "path": "tabs_raw/records.md", + "partial": false, + "record_count": 0 + }, + "balances": { + "path": "tabs_raw/balances.md", + "partial": false, + "record_count": 1289 + }, + "risk": { + "path": "tabs_raw/risk.md", + "partial": false, + "record_count": 31 + }, + "period_risks": { + "path": "tabs_raw/period_risks.md", + "partial": false, + "record_count": 10 + }, + "profile": { + "path": "tabs_raw/profile.md", + "partial": false, + "record_count": 7577 + }, + "logs": { + "path": "tabs_raw/logs.md", + "partial": true, + "record_count": 143 + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "partial": false, + "record_count": 1090 + } + }, + "note": "Generated from JoinQuant research get_backtest() schema v3 bundle. Platform logs are not provided by get_backtest().", + "integrity_status": "complete", + "integrity_issues": [], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1090 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/api_export.json new file mode 100644 index 00000000..08b905ba --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/api_export.json.gz", + "original_path": "api_export.json", + "original_sha256": "f7a967d3b67c2c52e049aeeaa3dafc0920f3008f0cd495e2ad5cb33eb4ae4221", + "compressed_sha256": "7c8fa08bc9602a269a706e74c2d6561377418719ac90c7ec2e2572ce59f5af34", + "original_bytes": 3356598 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/detail_api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/detail_api_export.json new file mode 100644 index 00000000..da9de39e --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/detail_api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/detail_api_export.json.gz", + "original_path": "detail_api_export.json", + "original_sha256": "82f066ba6c46bf633ff5487715febaee63804667bac0332d4d7da0d0805df0bf", + "compressed_sha256": "d96601104c203561628d12a21f6c79dc08ff11f2833fb7d3e3783ab25b6f2488", + "original_bytes": 4350900 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/integrity.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/integrity.json new file mode 100644 index 00000000..eeb967a3 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/integrity.json @@ -0,0 +1,153 @@ +{ + "status": "complete", + "generated_at": "2026-06-30T16:14:45.927252+00:00", + "issues": [], + "checks": [ + { + "name": "research_bundle", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": null, + "partial": null, + "message": "research get_backtest bundle is required" + }, + { + "name": "detail_api_bundle", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": null, + "partial": null, + "message": "detail API bundle is required" + }, + { + "name": "metadata", + "required": true, + "status": "pass", + "source": "metadata.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "summary_metrics", + "required": true, + "status": "pass", + "source": "summary_metrics.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "research_results", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 1289, + "partial": false, + "message": "" + }, + { + "name": "research_positions", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 2391, + "partial": false, + "message": "" + }, + { + "name": "research_orders", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 379, + "partial": false, + "message": "" + }, + { + "name": "research_risk", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 31, + "partial": false, + "message": "" + }, + { + "name": "detail_results", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 1485, + "partial": false, + "message": "" + }, + { + "name": "detail_transactions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 379, + "partial": false, + "message": "" + }, + { + "name": "detail_positions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 3680, + "partial": false, + "message": "" + }, + { + "name": "detail_risk_tabs", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 640, + "partial": null, + "message": "" + }, + { + "name": "platform_logs", + "required": false, + "status": "warn", + "source": "detail_api_export.json", + "count": 1000, + "partial": true, + "message": "platform logs may be partial; audit_log is canonical" + }, + { + "name": "audit_log", + "required": true, + "status": "pass", + "source": "tabs_raw/audit_log.jsonl", + "count": 1090, + "partial": false, + "message": "" + } + ], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1090 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/metadata.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/metadata.json new file mode 100644 index 00000000..e9a30323 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/metadata.json @@ -0,0 +1,26 @@ +{ + "strategy_name": "etf_factor_rotation", + "run_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "8d074a3098c6308f791ea33d8806d6f0", + "joinquant_backtest_id": "8d074a3098c6308f791ea33d8806d6f0", + "backtest_url": "", + "generated_at": "2026-07-01T00:14:34", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_8d074a3098c6308f791ea33d8806d6f0.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "234573daf021757752090358c356f903", + "audit_token": "etf_factor_rotation-s01-baseline-original-20260701001028-3e0b4107", + "audit_path": "jq_auto_audit/etf_factor_rotation-s01-baseline-original-20260701001028-3e0b4107.jsonl" +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md new file mode 100644 index 00000000..0a3f24d9 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md @@ -0,0 +1,32 @@ +# 回测数据汇总 + +- 策略名称:etf_factor_rotation +- 回测 ID:8d074a3098c6308f791ea33d8806d6f0 +- 区间:2021-01-01 至 2026-04-30 +- 提取方式:聚宽研究环境 get_backtest();详情页接口仅作补充。 + +## 核心指标 + +| 指标 | 值 | +| --- | --- | +| 策略收益 | 127.12% | +| 策略年化收益 | 17.25% | +| 基准收益 | -7.75% | +| 超额收益 | 146.21% | +| 最大回撤 | 7.60% | +| 夏普比率 | 1.582 | +| 阿尔法 | 0.140 | +| 贝塔 | 0.139 | +| 信息比率 | 1.084 | + +## 数据覆盖 + +| 数据 | 记录数 | 完整度 | +| --- | ---: | --- | +| 每日收益 | 1289 | 完整 | +| 每日持仓&收益 | 2391 | 完整 | +| 订单/交易 | 379 | 完整 | +| record 记录 | 0 | 完整 | +| 每日账户市值 | 1289 | 完整 | +| 分期风险 | 10 | 完整 | +| 平台日志 | | 详情页接口部分 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/data-integrity.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/data-integrity.md new file mode 100644 index 00000000..49ee492d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/data-integrity.md @@ -0,0 +1,26 @@ +# 数据完整性报告 + +- 状态:complete +- 生成时间:2026-06-30T16:14:45.927252+00:00 + +## 问题 +- 无 + +## 检查项 + +| 检查项 | 必须 | 状态 | 来源 | 记录数 | partial | 说明 | +| --- | --- | --- | --- | ---: | --- | --- | +| research_bundle | 是 | pass | api_export.json | | | research get_backtest bundle is required | +| detail_api_bundle | 是 | pass | detail_api_export.json | | | detail API bundle is required | +| metadata | 是 | pass | metadata.json | | | | +| summary_metrics | 是 | pass | summary_metrics.json | | | | +| research_results | 是 | pass | api_export.json | 1289 | False | | +| research_positions | 是 | pass | api_export.json | 2391 | False | | +| research_orders | 是 | pass | api_export.json | 379 | False | | +| research_risk | 是 | pass | api_export.json | 31 | False | | +| detail_results | 是 | pass | detail_api_export.json | 1485 | False | | +| detail_transactions | 是 | pass | detail_api_export.json | 379 | False | | +| detail_positions | 是 | pass | detail_api_export.json | 3680 | False | | +| detail_risk_tabs | 是 | pass | detail_api_export.json | 640 | | | +| platform_logs | 否 | warn | detail_api_export.json | 1000 | True | platform logs may be partial; audit_log is canonical | +| audit_log | 是 | pass | tabs_raw/audit_log.jsonl | 1090 | False | | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/summary_metrics.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/summary_metrics.json new file mode 100644 index 00000000..943b224b --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/summary_metrics.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/summary_metrics.json.gz", + "original_path": "summary_metrics.json", + "original_sha256": "74e9b525f50b3d66d1fdd881ac3cff7fa27b499260ba211f5392035121d16ea2", + "compressed_sha256": "b57ad5f2a7632af8588e874104c77b85537c143cbd613f9e7c3035081d7764a1", + "original_bytes": 520 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/audit_log.jsonl b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/audit_log.jsonl new file mode 100644 index 00000000..856b76a2 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/audit_log.jsonl @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/audit_log.jsonl.gz", + "original_path": "tabs_raw/audit_log.jsonl", + "original_sha256": "4b9c3166531201de33fccec8dce07ad33e80f4f70420cc15bb7aa2c9c56620ff", + "compressed_sha256": "424c1b1d9800c7a7c50d489ca18d8325b34a4bcd6013e8c8c978649c56fa0a3c", + "original_bytes": 1318032 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/balances.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/balances.md new file mode 100644 index 00000000..725ade46 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/balances.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/balances.md.gz", + "original_path": "tabs_raw/balances.md", + "original_sha256": "5d4c2b9f53deda2fbdd7e5f7c1cd0f8f9e13cc5b3540fea21d12e263a7937d74", + "compressed_sha256": "9bff925058a9f54ab8623d4ec4efa31ba12a76621731f5e532f9e546dbc03be8", + "original_bytes": 90889 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/daily_returns.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/daily_returns.md new file mode 100644 index 00000000..70ff19a9 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/daily_returns.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/daily_returns.md.gz", + "original_path": "tabs_raw/daily_returns.md", + "original_sha256": "73afb8b1c7783773a152041e2d7fd3a96ff1fec6cfc3bc7a8758f63c08073970", + "compressed_sha256": "c4b51782dd103013d6239ea85b91f3892829ad21546b10ffc05cc520457e5578", + "original_bytes": 92875 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/logs.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/logs.md new file mode 100644 index 00000000..be91cd0b --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/logs.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/logs.md.gz", + "original_path": "tabs_raw/logs.md", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "05269fff42e0b0e739e4003fbb5694a7160ca23692873edd5f4f022b419d0812", + "original_bytes": 290 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/period_risks.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/period_risks.md new file mode 100644 index 00000000..3e357b93 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/period_risks.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/period_risks.md.gz", + "original_path": "tabs_raw/period_risks.md", + "original_sha256": "00b68a33695b80da0837551ca441973d343584a66a44c0a43844f7412ddc7036", + "compressed_sha256": "90a9e983600498389a278aa856e10118fd99a6fe2e42eb61f046667a8d8afab6", + "original_bytes": 62839 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/positioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/positioninfo.md new file mode 100644 index 00000000..4d531020 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/positioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/positioninfo.md.gz", + "original_path": "tabs_raw/positioninfo.md", + "original_sha256": "e2f334231893babfb1b91d1076f5f407d541713c23a841a9443216e1a6057e8e", + "compressed_sha256": "3245ae8b76e39d33c4d89e7014de3980458d6da40764819dfb754ee08bc73f74", + "original_bytes": 379897 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/profile.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/profile.md new file mode 100644 index 00000000..77019b4d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/profile.md @@ -0,0 +1,501 @@ +# 性能分析 + +## fund_code + +- 总耗时:0.012156s + +```text +Total time: 0.012156 s +File: /tmp/strategy/user_code.py +Function: fund_code at line 128 +``` + +## format_etf_name + +- 总耗时:0.080615s + +```text +Total time: 0.080615 s +File: /tmp/strategy/user_code.py +Function: format_etf_name at line 130 +``` + +## build_etf_display_names + +- 总耗时:0.156018s + +```text +Total time: 0.156018 s +File: /tmp/strategy/user_code.py +Function: build_etf_display_names at line 143 +``` + +## fetch_etf_official_name + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: fetch_etf_official_name at line 150 +``` + +## load_etf_display_names + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: load_etf_display_names at line 160 +``` + +## _audit_jsonable + +- 总耗时:0.281633s + +```text +Total time: 0.281633 s +File: /tmp/strategy/user_code.py +Function: _audit_jsonable at line 168 +``` + +## _context_time_fields + +- 总耗时:0.197152s + +```text +Total time: 0.197152 s +File: /tmp/strategy/user_code.py +Function: _context_time_fields at line 197 +``` + +## audit_event + +- 总耗时:19.0657s + +```text +Total time: 19.0657 s +File: /tmp/strategy/user_code.py +Function: audit_event at line 204 +``` + +## _copy_runtime_default + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _copy_runtime_default at line 218 +``` + +## _runtime_param + +- 总耗时:0.028346s + +```text +Total time: 0.028346 s +File: /tmp/strategy/user_code.py +Function: _runtime_param at line 226 +``` + +## _runtime_list_param + +- 总耗时:0.012896s + +```text +Total time: 0.012896 s +File: /tmp/strategy/user_code.py +Function: _runtime_list_param at line 230 +``` + +## snapshot_params + +- 总耗时:0.129582s + +```text +Total time: 0.129582 s +File: /tmp/strategy/user_code.py +Function: snapshot_params at line 235 +``` + +## validate_params + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: validate_params at line 325 +``` + +## resolve_ma_long_windows + +- 总耗时:0.001979s + +```text +Total time: 0.001979 s +File: /tmp/strategy/user_code.py +Function: resolve_ma_long_windows at line 413 +``` + +## resolve_crowd_thresholds + +- 总耗时:0.003219s + +```text +Total time: 0.003219 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_thresholds at line 418 +``` + +## resolve_crowd_ret_windows + +- 总耗时:0.001763s + +```text +Total time: 0.001763 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_ret_windows at line 427 +``` + +## initialize + +- 总耗时:0.05712s + +```text +Total time: 0.05712 s +File: /tmp/strategy/user_code.py +Function: initialize at line 434 +``` + +## set_parameter + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: set_parameter at line 491 +``` + +## _log_step + +- 总耗时:0.907857s + +```text +Total time: 0.907857 s +File: /tmp/strategy/user_code.py +Function: _log_step at line 560 +``` + +## compose_raw_weights + +- 总耗时:0.004493s + +```text +Total time: 0.004493 s +File: /tmp/strategy/user_code.py +Function: compose_raw_weights at line 566 +``` + +## _context_trade_date + +- 总耗时:0.023234s + +```text +Total time: 0.023234 s +File: /tmp/strategy/user_code.py +Function: _context_trade_date at line 576 +``` + +## build_rebalance_plan + +- 总耗时:63.4462s + +```text +Total time: 63.4462 s +File: /tmp/strategy/user_code.py +Function: build_rebalance_plan at line 581 +``` + +## weekly_check + +- 总耗时:79.5893s + +```text +Total time: 79.5893 s +File: /tmp/strategy/user_code.py +Function: weekly_check at line 646 +``` + +## prepare_delay_only_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: prepare_delay_only_rebalance at line 649 +``` + +## execute_pending_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_pending_rebalance at line 660 +``` + +## mark_live_like_signal_day + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: mark_live_like_signal_day at line 688 +``` + +## execute_live_like_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_live_like_rebalance at line 697 +``` + +## normalize_field_frame + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: normalize_field_frame at line 735 +``` + +## compute_history_count + +- 总耗时:0.004626s + +```text +Total time: 0.004626 s +File: /tmp/strategy/user_code.py +Function: compute_history_count at line 744 +``` + +## fetch_field + +- 总耗时:30.9272s + +```text +Total time: 30.9272 s +File: /tmp/strategy/user_code.py +Function: fetch_field at line 754 +``` + +## get_history_data + +- 总耗时:32.0145s + +```text +Total time: 32.0145 s +File: /tmp/strategy/user_code.py +Function: get_history_data at line 774 +``` + +## compute_trend_gates + +- 总耗时:0.79665s + +```text +Total time: 0.79665 s +File: /tmp/strategy/user_code.py +Function: compute_trend_gates at line 786 +``` + +## compute_momentum_scores + +- 总耗时:1.96928s + +```text +Total time: 1.96928 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_scores at line 802 +``` + +## select_topk + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: select_topk at line 828 +``` + +## compute_rp_weights + +- 总耗时:0.463371s + +```text +Total time: 0.463371 s +File: /tmp/strategy/user_code.py +Function: compute_rp_weights at line 837 +``` + +## compute_rsrs_multipliers + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_multipliers at line 866 +``` + +## compute_rsrs_adjusted_scores + +- 总耗时:10.4507s + +```text +Total time: 10.4507 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_adjusted_scores at line 875 +``` + +## compute_momentum_tilt_multipliers + +- 总耗时:0.029982s + +```text +Total time: 0.029982 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_tilt_multipliers at line 918 +``` + +## compute_rsrs_tilt_multipliers + +- 总耗时:10.5222s + +```text +Total time: 10.5222 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_tilt_multipliers at line 939 +``` + +## apply_relative_tilts + +- 总耗时:0.01544s + +```text +Total time: 0.01544 s +File: /tmp/strategy/user_code.py +Function: apply_relative_tilts at line 956 +``` + +## compute_crowd_penalties + +- 总耗时:9.32494s + +```text +Total time: 9.32494 s +File: /tmp/strategy/user_code.py +Function: compute_crowd_penalties at line 972 +``` + +## percentile_rank + +- 总耗时:2.23243s + +```text +Total time: 2.23243 s +File: /tmp/strategy/user_code.py +Function: percentile_rank at line 1040 +``` + +## compute_portfolio_vol_scale_detail + +- 总耗时:0.36691s + +```text +Total time: 0.36691 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale_detail at line 1045 +``` + +## compute_portfolio_vol_scale + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale at line 1076 +``` + +## compute_portfolio_vol_asset_scales + +- 总耗时:0.922613s + +```text +Total time: 0.922613 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_asset_scales at line 1079 +``` + +## apply_fixed_gold_vol_relief + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_fixed_gold_vol_relief at line 1096 +``` + +## apply_dynamic_marginal_vol_relief + +- 总耗时:0.522912s + +```text +Total time: 0.522912 s +File: /tmp/strategy/user_code.py +Function: apply_dynamic_marginal_vol_relief at line 1119 +``` + +## select_dynamic_marginal_relief_asset + +- 总耗时:0.50953s + +```text +Total time: 0.50953 s +File: /tmp/strategy/user_code.py +Function: select_dynamic_marginal_relief_asset at line 1140 +``` + +## apply_weight_constraints + +- 总耗时:0.009596s + +```text +Total time: 0.009596 s +File: /tmp/strategy/user_code.py +Function: apply_weight_constraints at line 1175 +``` + +## execute_rebalance + +- 总耗时:16.039s + +```text +Total time: 16.039 s +File: /tmp/strategy/user_code.py +Function: execute_rebalance at line 1190 +``` diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/records.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/records.md new file mode 100644 index 00000000..8043b956 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/records.md @@ -0,0 +1,3 @@ +# Record 记录 + +(无数据) diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/risk.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/risk.md new file mode 100644 index 00000000..8a0e3018 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/risk.md @@ -0,0 +1,37 @@ +# 风险指标 + +- 记录数:31 + +| metric | value | +| --- | --- | +| __version | 101 | +| algorithm_return | 1.2711970657999987 | +| algorithm_volatility | 0.0837151468155677 | +| alpha | 0.14018014573154694 | +| annual_algo_return | 0.1724523289382165 | +| annual_bm_return | -0.01552798313301329 | +| avg_excess_return | 0.0007593171623250612 | +| avg_position_days | 797.0 | +| avg_trade_return | 0.05033239407731931 | +| benchmark_return | -0.07752074822165167 | +| benchmark_volatility | 0.17880949192018716 | +| beta | 0.13916977273997175 | +| day_win_ratio | 0.5415050426687354 | +| excess_return | 1.4620576142190638 | +| excess_return_max_drawdown | 0.21387413831505275 | +| excess_return_max_drawdown_period | ['2024-09-13', '2024-10-08'] | +| excess_return_sharpe | 0.8701734691377453 | +| information | 1.0838290169189766 | +| lose_count | 50 | +| max_drawdown | 0.07598434316029279 | +| max_drawdown_period | ['2021-11-22', '2022-01-28'] | +| max_leverage | 0.0 | +| period_label | 2026-04 | +| profit_loss_ratio | 4.96608883055434 | +| sharpe | 1.5821787809799983 | +| sortino | 2.3459743826632042 | +| trading_days | 1289 | +| treasury_return | 0.21282191780821919 | +| turnover_rate | 0.02053831413513158 | +| win_count | 142 | +| win_ratio | 0.7395833333333334 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/transactioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/transactioninfo.md new file mode 100644 index 00000000..30495f02 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/tabs_raw/transactioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "dataset_file": "raw/transactioninfo.md.gz", + "original_path": "tabs_raw/transactioninfo.md", + "original_sha256": "ec6ada5cd42a5306b4b1f739dce3be3df953f16ffc40a6262dc3c6579ac1809b", + "compressed_sha256": "a342acd478bf91911ecf7f9258321e5b305c5f5a651bc9dd04863eb104842620", + "original_bytes": 31967 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/all_data.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/all_data.json new file mode 100644 index 00000000..77b10f0b --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/all_data.json @@ -0,0 +1,105 @@ +{ + "api_export_json": "D:\\My Project\\Quant Trading\\strategies\\etf_factor_rotation\\backtest_runs\\20260701-0025-bt0d4e21c752867aa36b89ee13d7540352\\api_export.json", + "extraction_method": "research_bundle", + "generated_at": "2026-06-30T16:27:44.127711+00:00", + "counts": { + "results": 1289, + "positions": 2415, + "orders": 373, + "records": 0, + "balances": 1289, + "period_risk_tabs": 10, + "audit_log_lines": 1903 + }, + "partial": { + "results": false, + "positions": false, + "orders": false, + "records": false, + "risk": false, + "period_risks": false, + "balances": false, + "logs": true, + "audit_log": false + }, + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_0d4e21c752867aa36b89ee13d7540352.json", + "research_downloaded": true, + "detail_api_used": true, + "tabs": { + "daily_returns": { + "path": "tabs_raw/daily_returns.md", + "partial": false, + "record_count": 1289 + }, + "transactioninfo": { + "path": "tabs_raw/transactioninfo.md", + "partial": false, + "record_count": 373 + }, + "positioninfo": { + "path": "tabs_raw/positioninfo.md", + "partial": false, + "record_count": 2415 + }, + "records": { + "path": "tabs_raw/records.md", + "partial": false, + "record_count": 0 + }, + "balances": { + "path": "tabs_raw/balances.md", + "partial": false, + "record_count": 1289 + }, + "risk": { + "path": "tabs_raw/risk.md", + "partial": false, + "record_count": 31 + }, + "period_risks": { + "path": "tabs_raw/period_risks.md", + "partial": false, + "record_count": 10 + }, + "profile": { + "path": "tabs_raw/profile.md", + "partial": false, + "record_count": 8331 + }, + "logs": { + "path": "tabs_raw/logs.md", + "partial": true, + "record_count": 143 + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "partial": false, + "record_count": 1903 + } + }, + "note": "Generated from JoinQuant research get_backtest() schema v3 bundle. Platform logs are not provided by get_backtest().", + "integrity_status": "complete", + "integrity_issues": [], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1903 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/api_export.json new file mode 100644 index 00000000..c1e72d4c --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/api_export.json.gz", + "original_path": "api_export.json", + "original_sha256": "9bbff9ea33c7a3eefad1611590f0a759af491ef9a17819a873b228c6a74b48c3", + "compressed_sha256": "86eeeea86d10eabf501fd49cb8a9853f45547d03ac43242f24bce433ca7c89da", + "original_bytes": 3657616 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/detail_api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/detail_api_export.json new file mode 100644 index 00000000..d31cc19f --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/detail_api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/detail_api_export.json.gz", + "original_path": "detail_api_export.json", + "original_sha256": "e312b3f0bcd00a020c98131ff2a1178cc34ce07ca929b366d49f4098931a70da", + "compressed_sha256": "061144d15485e32f095850bc9afc983e604e6a14ef02cfee08625e34ea5d8c5a", + "original_bytes": 4383483 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/integrity.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/integrity.json new file mode 100644 index 00000000..7ddcbb6c --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/integrity.json @@ -0,0 +1,153 @@ +{ + "status": "complete", + "generated_at": "2026-06-30T16:27:44.168833+00:00", + "issues": [], + "checks": [ + { + "name": "research_bundle", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": null, + "partial": null, + "message": "research get_backtest bundle is required" + }, + { + "name": "detail_api_bundle", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": null, + "partial": null, + "message": "detail API bundle is required" + }, + { + "name": "metadata", + "required": true, + "status": "pass", + "source": "metadata.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "summary_metrics", + "required": true, + "status": "pass", + "source": "summary_metrics.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "research_results", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 1289, + "partial": false, + "message": "" + }, + { + "name": "research_positions", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 2415, + "partial": false, + "message": "" + }, + { + "name": "research_orders", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 373, + "partial": false, + "message": "" + }, + { + "name": "research_risk", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 31, + "partial": false, + "message": "" + }, + { + "name": "detail_results", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 1485, + "partial": false, + "message": "" + }, + { + "name": "detail_transactions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 373, + "partial": false, + "message": "" + }, + { + "name": "detail_positions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 3704, + "partial": false, + "message": "" + }, + { + "name": "detail_risk_tabs", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 640, + "partial": null, + "message": "" + }, + { + "name": "platform_logs", + "required": false, + "status": "warn", + "source": "detail_api_export.json", + "count": 1000, + "partial": true, + "message": "platform logs may be partial; audit_log is canonical" + }, + { + "name": "audit_log", + "required": true, + "status": "pass", + "source": "tabs_raw/audit_log.jsonl", + "count": 1903, + "partial": false, + "message": "" + } + ], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1903 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/metadata.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/metadata.json new file mode 100644 index 00000000..604512e0 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/metadata.json @@ -0,0 +1,26 @@ +{ + "strategy_name": "etf_factor_rotation", + "run_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "0d4e21c752867aa36b89ee13d7540352", + "joinquant_backtest_id": "0d4e21c752867aa36b89ee13d7540352", + "backtest_url": "", + "generated_at": "2026-07-01T00:27:32", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_0d4e21c752867aa36b89ee13d7540352.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "d79fe48b94af96dafe698c1361f19fa9", + "audit_token": "etf_factor_rotation-s02-logic3-code-variant-20260701002504-55d17183", + "audit_path": "jq_auto_audit/etf_factor_rotation-s02-logic3-code-variant-20260701002504-55d17183.jsonl" +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/backtest_report.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/backtest_report.md new file mode 100644 index 00000000..908dabfd --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/backtest_report.md @@ -0,0 +1,32 @@ +# 回测数据汇总 + +- 策略名称:etf_factor_rotation +- 回测 ID:0d4e21c752867aa36b89ee13d7540352 +- 区间:2021-01-01 至 2026-04-30 +- 提取方式:聚宽研究环境 get_backtest();详情页接口仅作补充。 + +## 核心指标 + +| 指标 | 值 | +| --- | --- | +| 策略收益 | 120.85% | +| 策略年化收益 | 16.61% | +| 基准收益 | -7.75% | +| 超额收益 | 139.41% | +| 最大回撤 | 11.66% | +| 夏普比率 | 1.543 | +| 阿尔法 | 0.134 | +| 贝塔 | 0.134 | +| 信息比率 | 1.047 | + +## 数据覆盖 + +| 数据 | 记录数 | 完整度 | +| --- | ---: | --- | +| 每日收益 | 1289 | 完整 | +| 每日持仓&收益 | 2415 | 完整 | +| 订单/交易 | 373 | 完整 | +| record 记录 | 0 | 完整 | +| 每日账户市值 | 1289 | 完整 | +| 分期风险 | 10 | 完整 | +| 平台日志 | | 详情页接口部分 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/data-integrity.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/data-integrity.md new file mode 100644 index 00000000..bde036c3 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/report/data-integrity.md @@ -0,0 +1,26 @@ +# 数据完整性报告 + +- 状态:complete +- 生成时间:2026-06-30T16:27:44.168833+00:00 + +## 问题 +- 无 + +## 检查项 + +| 检查项 | 必须 | 状态 | 来源 | 记录数 | partial | 说明 | +| --- | --- | --- | --- | ---: | --- | --- | +| research_bundle | 是 | pass | api_export.json | | | research get_backtest bundle is required | +| detail_api_bundle | 是 | pass | detail_api_export.json | | | detail API bundle is required | +| metadata | 是 | pass | metadata.json | | | | +| summary_metrics | 是 | pass | summary_metrics.json | | | | +| research_results | 是 | pass | api_export.json | 1289 | False | | +| research_positions | 是 | pass | api_export.json | 2415 | False | | +| research_orders | 是 | pass | api_export.json | 373 | False | | +| research_risk | 是 | pass | api_export.json | 31 | False | | +| detail_results | 是 | pass | detail_api_export.json | 1485 | False | | +| detail_transactions | 是 | pass | detail_api_export.json | 373 | False | | +| detail_positions | 是 | pass | detail_api_export.json | 3704 | False | | +| detail_risk_tabs | 是 | pass | detail_api_export.json | 640 | | | +| platform_logs | 否 | warn | detail_api_export.json | 1000 | True | platform logs may be partial; audit_log is canonical | +| audit_log | 是 | pass | tabs_raw/audit_log.jsonl | 1903 | False | | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/summary_metrics.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/summary_metrics.json new file mode 100644 index 00000000..3e4efc77 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/summary_metrics.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/summary_metrics.json.gz", + "original_path": "summary_metrics.json", + "original_sha256": "98d1bf471f920ed60c2e5b707c6ee82a302cda99ba5019d128585bfc2b298125", + "compressed_sha256": "77876f051fe717cab7f6fd148ef2ae02be048c0ed4206decdea1bc8e12a2c2c0", + "original_bytes": 521 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/audit_log.jsonl b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/audit_log.jsonl new file mode 100644 index 00000000..b89f5aa0 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/audit_log.jsonl @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/audit_log.jsonl.gz", + "original_path": "tabs_raw/audit_log.jsonl", + "original_sha256": "03b8321df76348480a1607a310b405d2a36e7893369f0c78106c82b85aeddca7", + "compressed_sha256": "caadfa85f8580e5666266e67582a5c70692a7f0fb360d4a7af126647f9c90bca", + "original_bytes": 1573538 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/balances.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/balances.md new file mode 100644 index 00000000..e915f228 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/balances.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/balances.md.gz", + "original_path": "tabs_raw/balances.md", + "original_sha256": "bb42c190a0c193426c36e3cae105bd27ce891614abf7f9e10f94dcb9a0aa6274", + "compressed_sha256": "eaf4d539a8607ab70f1d5bbab538b693c50d0d0854119e19b1cc42bebda8a378", + "original_bytes": 90295 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/daily_returns.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/daily_returns.md new file mode 100644 index 00000000..49d46b3b --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/daily_returns.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/daily_returns.md.gz", + "original_path": "tabs_raw/daily_returns.md", + "original_sha256": "3fe98f5676b51e9130b70628e7e4825777a354162acd2f36ea57e2c5d67128ce", + "compressed_sha256": "80b833a3f380632b700e3465a6f7152eb2ace77e9be03bd7afcfc9309185c20d", + "original_bytes": 92608 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/logs.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/logs.md new file mode 100644 index 00000000..64a36b42 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/logs.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/logs.md.gz", + "original_path": "tabs_raw/logs.md", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "05269fff42e0b0e739e4003fbb5694a7160ca23692873edd5f4f022b419d0812", + "original_bytes": 290 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/period_risks.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/period_risks.md new file mode 100644 index 00000000..451b2933 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/period_risks.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/period_risks.md.gz", + "original_path": "tabs_raw/period_risks.md", + "original_sha256": "12b8099715fc56c767744c28dd4a581e2245ba7f789cd44365fcfcaf3b55c7d1", + "compressed_sha256": "90f7def05b8d70d47e07c67810585f126e549cad52a7ede29c767ff3d01c1b3e", + "original_bytes": 62815 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/positioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/positioninfo.md new file mode 100644 index 00000000..5c8df49e --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/positioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/positioninfo.md.gz", + "original_path": "tabs_raw/positioninfo.md", + "original_sha256": "a3215e23b1ff949da0b0f11f24322d98f995f097c65ea821ff7ba849d4e7fb68", + "compressed_sha256": "14b9310ec01355c6aa1e012fabfc15a71e0df847de00122774e13ebfd3aad8af", + "original_bytes": 379635 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/profile.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/profile.md new file mode 100644 index 00000000..38620882 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/profile.md @@ -0,0 +1,551 @@ +# 性能分析 + +## fund_code + +- 总耗时:0.013006s + +```text +Total time: 0.013006 s +File: /tmp/strategy/user_code.py +Function: fund_code at line 129 +``` + +## format_etf_name + +- 总耗时:0.086733s + +```text +Total time: 0.086733 s +File: /tmp/strategy/user_code.py +Function: format_etf_name at line 131 +``` + +## build_etf_display_names + +- 总耗时:0.169459s + +```text +Total time: 0.169459 s +File: /tmp/strategy/user_code.py +Function: build_etf_display_names at line 144 +``` + +## fetch_etf_official_name + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: fetch_etf_official_name at line 151 +``` + +## load_etf_display_names + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: load_etf_display_names at line 161 +``` + +## _audit_jsonable + +- 总耗时:0.264367s + +```text +Total time: 0.264367 s +File: /tmp/strategy/user_code.py +Function: _audit_jsonable at line 169 +``` + +## _context_time_fields + +- 总耗时:0.304284s + +```text +Total time: 0.304284 s +File: /tmp/strategy/user_code.py +Function: _context_time_fields at line 198 +``` + +## audit_event + +- 总耗时:26.9545s + +```text +Total time: 26.9545 s +File: /tmp/strategy/user_code.py +Function: audit_event at line 205 +``` + +## _copy_runtime_default + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _copy_runtime_default at line 219 +``` + +## _runtime_param + +- 总耗时:0.078164s + +```text +Total time: 0.078164 s +File: /tmp/strategy/user_code.py +Function: _runtime_param at line 227 +``` + +## _runtime_list_param + +- 总耗时:0.034155s + +```text +Total time: 0.034155 s +File: /tmp/strategy/user_code.py +Function: _runtime_list_param at line 231 +``` + +## snapshot_params + +- 总耗时:0.385082s + +```text +Total time: 0.385082 s +File: /tmp/strategy/user_code.py +Function: snapshot_params at line 236 +``` + +## validate_params + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: validate_params at line 326 +``` + +## resolve_ma_long_windows + +- 总耗时:0.001854s + +```text +Total time: 0.001854 s +File: /tmp/strategy/user_code.py +Function: resolve_ma_long_windows at line 414 +``` + +## resolve_crowd_thresholds + +- 总耗时:0.003031s + +```text +Total time: 0.003031 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_thresholds at line 419 +``` + +## resolve_crowd_ret_windows + +- 总耗时:0.001444s + +```text +Total time: 0.001444 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_ret_windows at line 428 +``` + +## initialize + +- 总耗时:0.041832s + +```text +Total time: 0.041832 s +File: /tmp/strategy/user_code.py +Function: initialize at line 435 +``` + +## set_parameter + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: set_parameter at line 503 +``` + +## _log_step + +- 总耗时:0.785075s + +```text +Total time: 0.785075 s +File: /tmp/strategy/user_code.py +Function: _log_step at line 572 +``` + +## compose_raw_weights + +- 总耗时:0.003935s + +```text +Total time: 0.003935 s +File: /tmp/strategy/user_code.py +Function: compose_raw_weights at line 578 +``` + +## _as_date + +- 总耗时:0.004534s + +```text +Total time: 0.004534 s +File: /tmp/strategy/user_code.py +Function: _as_date at line 588 +``` + +## _context_trade_date + +- 总耗时:0.103082s + +```text +Total time: 0.103082 s +File: /tmp/strategy/user_code.py +Function: _context_trade_date at line 599 +``` + +## _next_trade_date + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _next_trade_date at line 601 +``` + +## _same_iso_week + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _same_iso_week at line 619 +``` + +## build_rebalance_plan + +- 总耗时:51.9012s + +```text +Total time: 51.9012 s +File: /tmp/strategy/user_code.py +Function: build_rebalance_plan at line 625 +``` + +## weekly_check + +- 总耗时:66.0101s + +```text +Total time: 66.0101 s +File: /tmp/strategy/user_code.py +Function: weekly_check at line 694 +``` + +## prepare_delay_only_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: prepare_delay_only_rebalance at line 697 +``` + +## execute_pending_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_pending_rebalance at line 708 +``` + +## mark_live_like_signal_day + +- 总耗时:3.72377s + +```text +Total time: 3.72377 s +File: /tmp/strategy/user_code.py +Function: mark_live_like_signal_day at line 736 +``` + +## execute_live_like_rebalance + +- 总耗时:73.3181s + +```text +Total time: 73.3181 s +File: /tmp/strategy/user_code.py +Function: execute_live_like_rebalance at line 745 +``` + +## prepare_weekend_close_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: prepare_weekend_close_rebalance at line 783 +``` + +## execute_weekend_close_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_weekend_close_rebalance at line 822 +``` + +## normalize_field_frame + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: normalize_field_frame at line 853 +``` + +## compute_history_count + +- 总耗时:0.004012s + +```text +Total time: 0.004012 s +File: /tmp/strategy/user_code.py +Function: compute_history_count at line 862 +``` + +## fetch_field + +- 总耗时:24.5892s + +```text +Total time: 24.5892 s +File: /tmp/strategy/user_code.py +Function: fetch_field at line 872 +``` + +## get_history_data + +- 总耗时:25.3159s + +```text +Total time: 25.3159 s +File: /tmp/strategy/user_code.py +Function: get_history_data at line 892 +``` + +## compute_trend_gates + +- 总耗时:0.658139s + +```text +Total time: 0.658139 s +File: /tmp/strategy/user_code.py +Function: compute_trend_gates at line 905 +``` + +## compute_momentum_scores + +- 总耗时:1.60766s + +```text +Total time: 1.60766 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_scores at line 921 +``` + +## select_topk + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: select_topk at line 947 +``` + +## compute_rp_weights + +- 总耗时:0.394105s + +```text +Total time: 0.394105 s +File: /tmp/strategy/user_code.py +Function: compute_rp_weights at line 956 +``` + +## compute_rsrs_multipliers + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_multipliers at line 985 +``` + +## compute_rsrs_adjusted_scores + +- 总耗时:8.77132s + +```text +Total time: 8.77132 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_adjusted_scores at line 994 +``` + +## compute_momentum_tilt_multipliers + +- 总耗时:0.025801s + +```text +Total time: 0.025801 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_tilt_multipliers at line 1037 +``` + +## compute_rsrs_tilt_multipliers + +- 总耗时:8.83197s + +```text +Total time: 8.83197 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_tilt_multipliers at line 1058 +``` + +## apply_relative_tilts + +- 总耗时:0.013144s + +```text +Total time: 0.013144 s +File: /tmp/strategy/user_code.py +Function: apply_relative_tilts at line 1075 +``` + +## compute_crowd_penalties + +- 总耗时:8.08886s + +```text +Total time: 8.08886 s +File: /tmp/strategy/user_code.py +Function: compute_crowd_penalties at line 1091 +``` + +## percentile_rank + +- 总耗时:1.95519s + +```text +Total time: 1.95519 s +File: /tmp/strategy/user_code.py +Function: percentile_rank at line 1159 +``` + +## compute_portfolio_vol_scale_detail + +- 总耗时:0.325778s + +```text +Total time: 0.325778 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale_detail at line 1164 +``` + +## compute_portfolio_vol_scale + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale at line 1195 +``` + +## compute_portfolio_vol_asset_scales + +- 总耗时:0.823519s + +```text +Total time: 0.823519 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_asset_scales at line 1198 +``` + +## apply_fixed_gold_vol_relief + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_fixed_gold_vol_relief at line 1215 +``` + +## apply_dynamic_marginal_vol_relief + +- 总耗时:0.469061s + +```text +Total time: 0.469061 s +File: /tmp/strategy/user_code.py +Function: apply_dynamic_marginal_vol_relief at line 1238 +``` + +## select_dynamic_marginal_relief_asset + +- 总耗时:0.456829s + +```text +Total time: 0.456829 s +File: /tmp/strategy/user_code.py +Function: select_dynamic_marginal_relief_asset at line 1259 +``` + +## apply_weight_constraints + +- 总耗时:0.008202s + +```text +Total time: 0.008202 s +File: /tmp/strategy/user_code.py +Function: apply_weight_constraints at line 1294 +``` + +## execute_rebalance + +- 总耗时:14.0182s + +```text +Total time: 14.0182 s +File: /tmp/strategy/user_code.py +Function: execute_rebalance at line 1309 +``` diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/records.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/records.md new file mode 100644 index 00000000..8043b956 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/records.md @@ -0,0 +1,3 @@ +# Record 记录 + +(无数据) diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/risk.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/risk.md new file mode 100644 index 00000000..dbaa8a5a --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/risk.md @@ -0,0 +1,37 @@ +# 风险指标 + +- 记录数:31 + +| metric | value | +| --- | --- | +| __version | 101 | +| algorithm_return | 1.2084720048999968 | +| algorithm_volatility | 0.08171770998671299 | +| alpha | 0.13353282713269088 | +| annual_algo_return | 0.1661011131707033 | +| annual_bm_return | -0.01552798313301329 | +| avg_excess_return | 0.0007376028973042972 | +| avg_position_days | 805.0 | +| avg_trade_return | 0.04831229742308037 | +| benchmark_return | -0.07752074822165167 | +| benchmark_volatility | 0.17880949192018716 | +| beta | 0.13383727523806196 | +| day_win_ratio | 0.5446082234290147 | +| excess_return | 1.394061438934834 | +| excess_return_max_drawdown | 0.2099277608370691 | +| excess_return_max_drawdown_period | ['2024-09-13', '2024-10-08'] | +| excess_return_sharpe | 0.8327248575800921 | +| information | 1.047026560009065 | +| lose_count | 55 | +| max_drawdown | 0.11659213666185808 | +| max_drawdown_period | ['2021-09-06', '2022-10-21'] | +| max_leverage | 0.0 | +| period_label | 2026-04 | +| profit_loss_ratio | 4.223695667978375 | +| sharpe | 1.543130775338747 | +| sortino | 2.2760548147608937 | +| trading_days | 1289 | +| treasury_return | 0.21282191780821919 | +| turnover_rate | 0.020794991631749023 | +| win_count | 132 | +| win_ratio | 0.7058823529411765 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/transactioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/transactioninfo.md new file mode 100644 index 00000000..28868216 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352/tabs_raw/transactioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "dataset_file": "raw/transactioninfo.md.gz", + "original_path": "tabs_raw/transactioninfo.md", + "original_sha256": "e782081b37742ef6b6be217827ed0afb4429871c018b0baf58a77329cb30704d", + "compressed_sha256": "37c49aa9b9d47b49651e4e6cdf114c06dbf0be15fe5c8ead933c576b0968944c", + "original_bytes": 31319 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/all_data.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/all_data.json new file mode 100644 index 00000000..bd09934d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/all_data.json @@ -0,0 +1,105 @@ +{ + "api_export_json": "D:\\My Project\\Quant Trading\\strategies\\etf_factor_rotation\\backtest_runs\\20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5\\api_export.json", + "extraction_method": "research_bundle", + "generated_at": "2026-06-30T16:29:20.801482+00:00", + "counts": { + "results": 1289, + "positions": 0, + "orders": 0, + "records": 0, + "balances": 1289, + "period_risk_tabs": 10, + "audit_log_lines": 1291 + }, + "partial": { + "results": false, + "positions": false, + "orders": false, + "records": false, + "risk": false, + "period_risks": false, + "balances": false, + "logs": true, + "audit_log": false + }, + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_cb35093c739b769b0c7cebb1bbe141b5.json", + "research_downloaded": true, + "detail_api_used": true, + "tabs": { + "daily_returns": { + "path": "tabs_raw/daily_returns.md", + "partial": false, + "record_count": 1289 + }, + "transactioninfo": { + "path": "tabs_raw/transactioninfo.md", + "partial": false, + "record_count": 0 + }, + "positioninfo": { + "path": "tabs_raw/positioninfo.md", + "partial": false, + "record_count": 0 + }, + "records": { + "path": "tabs_raw/records.md", + "partial": false, + "record_count": 0 + }, + "balances": { + "path": "tabs_raw/balances.md", + "partial": false, + "record_count": 1289 + }, + "risk": { + "path": "tabs_raw/risk.md", + "partial": false, + "record_count": 31 + }, + "period_risks": { + "path": "tabs_raw/period_risks.md", + "partial": false, + "record_count": 10 + }, + "profile": { + "path": "tabs_raw/profile.md", + "partial": false, + "record_count": 8013 + }, + "logs": { + "path": "tabs_raw/logs.md", + "partial": true, + "record_count": 143 + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "partial": false, + "record_count": 1291 + } + }, + "note": "Generated from JoinQuant research get_backtest() schema v3 bundle. Platform logs are not provided by get_backtest().", + "integrity_status": "complete", + "integrity_issues": [], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1291 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/api_export.json new file mode 100644 index 00000000..1590e605 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/api_export.json.gz", + "original_path": "api_export.json", + "original_sha256": "c229ecd0a002776c46a8ac61b45afe338320d96f56256f86fba24ae5d1913675", + "compressed_sha256": "4a0508b9259be6e1c383b046076360cf9d4bb33584736686522a9fdfd22b3117", + "original_bytes": 1195328 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/detail_api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/detail_api_export.json new file mode 100644 index 00000000..0d13e015 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/detail_api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/detail_api_export.json.gz", + "original_path": "detail_api_export.json", + "original_sha256": "cf5dbcd96ca03765831910e39f65b0dd4677bbdc53d0cd81183fab54e1c1c077", + "compressed_sha256": "f30a669cd2bf68d772ffe54597f4bd96cd469b580511c438af482e39ce850dc0", + "original_bytes": 1929553 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/integrity.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/integrity.json new file mode 100644 index 00000000..5d61e7f1 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/integrity.json @@ -0,0 +1,153 @@ +{ + "status": "complete", + "generated_at": "2026-06-30T16:29:20.812415+00:00", + "issues": [], + "checks": [ + { + "name": "research_bundle", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": null, + "partial": null, + "message": "research get_backtest bundle is required" + }, + { + "name": "detail_api_bundle", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": null, + "partial": null, + "message": "detail API bundle is required" + }, + { + "name": "metadata", + "required": true, + "status": "pass", + "source": "metadata.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "summary_metrics", + "required": true, + "status": "pass", + "source": "summary_metrics.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "research_results", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 1289, + "partial": false, + "message": "" + }, + { + "name": "research_positions", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 0, + "partial": false, + "message": "" + }, + { + "name": "research_orders", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 0, + "partial": false, + "message": "" + }, + { + "name": "research_risk", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 31, + "partial": false, + "message": "" + }, + { + "name": "detail_results", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 1485, + "partial": false, + "message": "" + }, + { + "name": "detail_transactions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 0, + "partial": false, + "message": "" + }, + { + "name": "detail_positions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 1289, + "partial": false, + "message": "" + }, + { + "name": "detail_risk_tabs", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 640, + "partial": null, + "message": "" + }, + { + "name": "platform_logs", + "required": false, + "status": "warn", + "source": "detail_api_export.json", + "count": 1000, + "partial": true, + "message": "platform logs may be partial; audit_log is canonical" + }, + { + "name": "audit_log", + "required": true, + "status": "pass", + "source": "tabs_raw/audit_log.jsonl", + "count": 1291, + "partial": false, + "message": "" + } + ], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1291 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/metadata.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/metadata.json new file mode 100644 index 00000000..bc8c9cbf --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/metadata.json @@ -0,0 +1,26 @@ +{ + "strategy_name": "etf_factor_rotation", + "run_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "cb35093c739b769b0c7cebb1bbe141b5", + "joinquant_backtest_id": "cb35093c739b769b0c7cebb1bbe141b5", + "backtest_url": "", + "generated_at": "2026-07-01T00:29:10", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_cb35093c739b769b0c7cebb1bbe141b5.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "34a1ef4f300c8c4763d455b4dd5fdc1f", + "audit_token": "etf_factor_rotation-s03-weekend-close-signal-next-open-variant-20260701002745-dae632f4", + "audit_path": "jq_auto_audit/etf_factor_rotation-s03-weekend-close-signal-next-open-variant-20260701002745-dae632f4.jsonl" +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/backtest_report.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/backtest_report.md new file mode 100644 index 00000000..cd963ebc --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/backtest_report.md @@ -0,0 +1,32 @@ +# 回测数据汇总 + +- 策略名称:etf_factor_rotation +- 回测 ID:cb35093c739b769b0c7cebb1bbe141b5 +- 区间:2021-01-01 至 2026-04-30 +- 提取方式:聚宽研究环境 get_backtest();详情页接口仅作补充。 + +## 核心指标 + +| 指标 | 值 | +| --- | --- | +| 策略收益 | 0.00% | +| 策略年化收益 | 0.00% | +| 基准收益 | -7.75% | +| 超额收益 | 8.40% | +| 最大回撤 | 0.00% | +| 夏普比率 | 0.000 | +| 阿尔法 | -0.040 | +| 贝塔 | 0.000 | +| 信息比率 | 0.087 | + +## 数据覆盖 + +| 数据 | 记录数 | 完整度 | +| --- | ---: | --- | +| 每日收益 | 1289 | 完整 | +| 每日持仓&收益 | 0 | 完整 | +| 订单/交易 | 0 | 完整 | +| record 记录 | 0 | 完整 | +| 每日账户市值 | 1289 | 完整 | +| 分期风险 | 10 | 完整 | +| 平台日志 | | 详情页接口部分 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/data-integrity.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/data-integrity.md new file mode 100644 index 00000000..6928ce73 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/report/data-integrity.md @@ -0,0 +1,26 @@ +# 数据完整性报告 + +- 状态:complete +- 生成时间:2026-06-30T16:29:20.812415+00:00 + +## 问题 +- 无 + +## 检查项 + +| 检查项 | 必须 | 状态 | 来源 | 记录数 | partial | 说明 | +| --- | --- | --- | --- | ---: | --- | --- | +| research_bundle | 是 | pass | api_export.json | | | research get_backtest bundle is required | +| detail_api_bundle | 是 | pass | detail_api_export.json | | | detail API bundle is required | +| metadata | 是 | pass | metadata.json | | | | +| summary_metrics | 是 | pass | summary_metrics.json | | | | +| research_results | 是 | pass | api_export.json | 1289 | False | | +| research_positions | 是 | pass | api_export.json | 0 | False | | +| research_orders | 是 | pass | api_export.json | 0 | False | | +| research_risk | 是 | pass | api_export.json | 31 | False | | +| detail_results | 是 | pass | detail_api_export.json | 1485 | False | | +| detail_transactions | 是 | pass | detail_api_export.json | 0 | False | | +| detail_positions | 是 | pass | detail_api_export.json | 1289 | False | | +| detail_risk_tabs | 是 | pass | detail_api_export.json | 640 | | | +| platform_logs | 否 | warn | detail_api_export.json | 1000 | True | platform logs may be partial; audit_log is canonical | +| audit_log | 是 | pass | tabs_raw/audit_log.jsonl | 1291 | False | | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/summary_metrics.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/summary_metrics.json new file mode 100644 index 00000000..0e543eca --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/summary_metrics.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/summary_metrics.json.gz", + "original_path": "summary_metrics.json", + "original_sha256": "1441610d84c772df69020a9b419b6a0d93d17925a536a0da73302043f938622c", + "compressed_sha256": "d93b7bc879c5078a26700fa2537df9dccb923afaa938a58b2b2fb65c8c3effc5", + "original_bytes": 513 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/audit_log.jsonl b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/audit_log.jsonl new file mode 100644 index 00000000..70cdd7be --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/audit_log.jsonl @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/audit_log.jsonl.gz", + "original_path": "tabs_raw/audit_log.jsonl", + "original_sha256": "c2bc4e50e30b7d10d5e4442072d4c941a72ad973444e5f280f79185fdba4bdb7", + "compressed_sha256": "8666e944cd6e4c73d8110c1285e631d5e7f19923bf06479d59ce80097cc82695", + "original_bytes": 457444 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/balances.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/balances.md new file mode 100644 index 00000000..fe1bf791 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/balances.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/balances.md.gz", + "original_path": "tabs_raw/balances.md", + "original_sha256": "ee7877994ae8bba34c4f473a8486c5735751380e0d895811c34bc3b4c999af0f", + "compressed_sha256": "08cb8867e0c9f219c8a89a87f77d0f79bc997c0e78f0fe9f78fe04ef8db02063", + "original_bytes": 89075 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/daily_returns.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/daily_returns.md new file mode 100644 index 00000000..c9f04737 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/daily_returns.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/daily_returns.md.gz", + "original_path": "tabs_raw/daily_returns.md", + "original_sha256": "280364f893898ccb15f694c6d4100181227cc07f7286799990f3490b5a1bce6c", + "compressed_sha256": "047a3b8cd21fbde23a45ed77f444e16f52b31ec64294acf1b81bb6906b429855", + "original_bytes": 71979 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/logs.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/logs.md new file mode 100644 index 00000000..71b7b09d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/logs.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/logs.md.gz", + "original_path": "tabs_raw/logs.md", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "05269fff42e0b0e739e4003fbb5694a7160ca23692873edd5f4f022b419d0812", + "original_bytes": 290 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/period_risks.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/period_risks.md new file mode 100644 index 00000000..eb78cad7 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/period_risks.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/period_risks.md.gz", + "original_path": "tabs_raw/period_risks.md", + "original_sha256": "07dcbe34d02a74d975ee511e851dd632d867e5d7bbf78e0273902c5ee6b7a679", + "compressed_sha256": "98f73b5d0a3d9998b7874456af774414660a139bc075a11ec21bbb2dfc8f167c", + "original_bytes": 37103 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/positioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/positioninfo.md new file mode 100644 index 00000000..7410794c --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/positioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/positioninfo.md.gz", + "original_path": "tabs_raw/positioninfo.md", + "original_sha256": "2e84a9b6856d1079b7ef24a3ddace913452650e6d3ed7f1ebda4c606bb982540", + "compressed_sha256": "d051b390de0ac7ab1028c8ffad73921af3d4920ba4dbe9593b0858a2c6820b7c", + "original_bytes": 46 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/profile.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/profile.md new file mode 100644 index 00000000..f46bd3fd --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/profile.md @@ -0,0 +1,551 @@ +# 性能分析 + +## fund_code + +- 总耗时:0.004261s + +```text +Total time: 0.004261 s +File: /tmp/strategy/user_code.py +Function: fund_code at line 129 +``` + +## format_etf_name + +- 总耗时:0.03124s + +```text +Total time: 0.03124 s +File: /tmp/strategy/user_code.py +Function: format_etf_name at line 131 +``` + +## build_etf_display_names + +- 总耗时:0.060627s + +```text +Total time: 0.060627 s +File: /tmp/strategy/user_code.py +Function: build_etf_display_names at line 144 +``` + +## fetch_etf_official_name + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: fetch_etf_official_name at line 151 +``` + +## load_etf_display_names + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: load_etf_display_names at line 161 +``` + +## _audit_jsonable + +- 总耗时:0.034697s + +```text +Total time: 0.034697 s +File: /tmp/strategy/user_code.py +Function: _audit_jsonable at line 169 +``` + +## _context_time_fields + +- 总耗时:0.225254s + +```text +Total time: 0.225254 s +File: /tmp/strategy/user_code.py +Function: _context_time_fields at line 198 +``` + +## audit_event + +- 总耗时:18.3511s + +```text +Total time: 18.3511 s +File: /tmp/strategy/user_code.py +Function: audit_event at line 205 +``` + +## _copy_runtime_default + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _copy_runtime_default at line 219 +``` + +## _runtime_param + +- 总耗时:0.090604s + +```text +Total time: 0.090604 s +File: /tmp/strategy/user_code.py +Function: _runtime_param at line 227 +``` + +## _runtime_list_param + +- 总耗时:0.041924s + +```text +Total time: 0.041924 s +File: /tmp/strategy/user_code.py +Function: _runtime_list_param at line 231 +``` + +## snapshot_params + +- 总耗时:0.447207s + +```text +Total time: 0.447207 s +File: /tmp/strategy/user_code.py +Function: snapshot_params at line 236 +``` + +## validate_params + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: validate_params at line 326 +``` + +## resolve_ma_long_windows + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: resolve_ma_long_windows at line 414 +``` + +## resolve_crowd_thresholds + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_thresholds at line 419 +``` + +## resolve_crowd_ret_windows + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_ret_windows at line 428 +``` + +## initialize + +- 总耗时:0.045607s + +```text +Total time: 0.045607 s +File: /tmp/strategy/user_code.py +Function: initialize at line 435 +``` + +## set_parameter + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: set_parameter at line 503 +``` + +## _log_step + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _log_step at line 572 +``` + +## compose_raw_weights + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compose_raw_weights at line 578 +``` + +## _as_date + +- 总耗时:0.008862s + +```text +Total time: 0.008862 s +File: /tmp/strategy/user_code.py +Function: _as_date at line 588 +``` + +## _context_trade_date + +- 总耗时:0.093882s + +```text +Total time: 0.093882 s +File: /tmp/strategy/user_code.py +Function: _context_trade_date at line 599 +``` + +## _next_trade_date + +- 总耗时:0.30164s + +```text +Total time: 0.30164 s +File: /tmp/strategy/user_code.py +Function: _next_trade_date at line 601 +``` + +## _same_iso_week + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _same_iso_week at line 619 +``` + +## build_rebalance_plan + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: build_rebalance_plan at line 625 +``` + +## weekly_check + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: weekly_check at line 694 +``` + +## prepare_delay_only_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: prepare_delay_only_rebalance at line 697 +``` + +## execute_pending_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_pending_rebalance at line 708 +``` + +## mark_live_like_signal_day + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: mark_live_like_signal_day at line 736 +``` + +## execute_live_like_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_live_like_rebalance at line 745 +``` + +## prepare_weekend_close_rebalance + +- 总耗时:19.4619s + +```text +Total time: 19.4619 s +File: /tmp/strategy/user_code.py +Function: prepare_weekend_close_rebalance at line 783 +``` + +## execute_weekend_close_rebalance + +- 总耗时:0.009116s + +```text +Total time: 0.009116 s +File: /tmp/strategy/user_code.py +Function: execute_weekend_close_rebalance at line 822 +``` + +## normalize_field_frame + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: normalize_field_frame at line 853 +``` + +## compute_history_count + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_history_count at line 862 +``` + +## fetch_field + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: fetch_field at line 872 +``` + +## get_history_data + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: get_history_data at line 892 +``` + +## compute_trend_gates + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_trend_gates at line 905 +``` + +## compute_momentum_scores + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_scores at line 921 +``` + +## select_topk + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: select_topk at line 947 +``` + +## compute_rp_weights + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rp_weights at line 956 +``` + +## compute_rsrs_multipliers + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_multipliers at line 985 +``` + +## compute_rsrs_adjusted_scores + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_adjusted_scores at line 994 +``` + +## compute_momentum_tilt_multipliers + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_tilt_multipliers at line 1037 +``` + +## compute_rsrs_tilt_multipliers + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_tilt_multipliers at line 1058 +``` + +## apply_relative_tilts + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_relative_tilts at line 1075 +``` + +## compute_crowd_penalties + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_crowd_penalties at line 1091 +``` + +## percentile_rank + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: percentile_rank at line 1159 +``` + +## compute_portfolio_vol_scale_detail + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale_detail at line 1164 +``` + +## compute_portfolio_vol_scale + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale at line 1195 +``` + +## compute_portfolio_vol_asset_scales + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_asset_scales at line 1198 +``` + +## apply_fixed_gold_vol_relief + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_fixed_gold_vol_relief at line 1215 +``` + +## apply_dynamic_marginal_vol_relief + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_dynamic_marginal_vol_relief at line 1238 +``` + +## select_dynamic_marginal_relief_asset + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: select_dynamic_marginal_relief_asset at line 1259 +``` + +## apply_weight_constraints + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_weight_constraints at line 1294 +``` + +## execute_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_rebalance at line 1309 +``` diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/records.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/records.md new file mode 100644 index 00000000..8043b956 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/records.md @@ -0,0 +1,3 @@ +# Record 记录 + +(无数据) diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/risk.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/risk.md new file mode 100644 index 00000000..07b42ec6 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/risk.md @@ -0,0 +1,37 @@ +# 风险指标 + +- 记录数:31 + +| metric | value | +| --- | --- | +| __version | 101 | +| algorithm_return | 0.0 | +| algorithm_volatility | 0.0 | +| alpha | -0.04 | +| annual_algo_return | 0.0 | +| annual_bm_return | -0.01552798313301329 | +| avg_excess_return | 0.00012648312541689298 | +| avg_position_days | 0 | +| avg_trade_return | 0.0 | +| benchmark_return | -0.07752074822165167 | +| benchmark_volatility | 0.17880949192018716 | +| beta | 0.0 | +| day_win_ratio | 0.5050426687354539 | +| excess_return | 0.0840352214667277 | +| excess_return_max_drawdown | 0.34323862055018295 | +| excess_return_max_drawdown_period | ['2024-09-13', '2026-04-29'] | +| excess_return_sharpe | -0.13545975391258516 | +| information | 0.08684093314209691 | +| lose_count | 0 | +| max_drawdown | 0.0 | +| max_drawdown_period | ['2021-01-04', '2021-01-04'] | +| max_leverage | 0.0 | +| period_label | 2026-04 | +| profit_loss_ratio | 0.0 | +| sharpe | 0.0 | +| sortino | 0.0 | +| trading_days | 1289 | +| treasury_return | 0.21282191780821919 | +| turnover_rate | 0.0 | +| win_count | 0 | +| win_ratio | 0 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/transactioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/transactioninfo.md new file mode 100644 index 00000000..05d72590 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5/tabs_raw/transactioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "dataset_file": "raw/transactioninfo.md.gz", + "original_path": "tabs_raw/transactioninfo.md", + "original_sha256": "aef37d19402efee5cf279ac02e7962f8eacdc8c878fc5ff6265185aaf09e6a8e", + "compressed_sha256": "b4b0eae5867c88eeaabb8404edf7c499cbdadef927d80bc4f8e97afdbbbd821d", + "original_bytes": 37 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/all_data.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/all_data.json new file mode 100644 index 00000000..c4133145 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/all_data.json @@ -0,0 +1,105 @@ +{ + "api_export_json": "D:\\My Project\\Quant Trading\\strategies\\etf_factor_rotation\\backtest_runs\\20260701-0032-btdcc6252aadef8bb09592538da4dde54c\\api_export.json", + "extraction_method": "research_bundle", + "generated_at": "2026-06-30T16:38:34.484953+00:00", + "counts": { + "results": 1289, + "positions": 2386, + "orders": 378, + "records": 0, + "balances": 1289, + "period_risk_tabs": 10, + "audit_log_lines": 4681 + }, + "partial": { + "results": false, + "positions": false, + "orders": false, + "records": false, + "risk": false, + "period_risks": false, + "balances": false, + "logs": true, + "audit_log": false + }, + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_dcc6252aadef8bb09592538da4dde54c.json", + "research_downloaded": true, + "detail_api_used": true, + "tabs": { + "daily_returns": { + "path": "tabs_raw/daily_returns.md", + "partial": false, + "record_count": 1289 + }, + "transactioninfo": { + "path": "tabs_raw/transactioninfo.md", + "partial": false, + "record_count": 378 + }, + "positioninfo": { + "path": "tabs_raw/positioninfo.md", + "partial": false, + "record_count": 2386 + }, + "records": { + "path": "tabs_raw/records.md", + "partial": false, + "record_count": 0 + }, + "balances": { + "path": "tabs_raw/balances.md", + "partial": false, + "record_count": 1289 + }, + "risk": { + "path": "tabs_raw/risk.md", + "partial": false, + "record_count": 31 + }, + "period_risks": { + "path": "tabs_raw/period_risks.md", + "partial": false, + "record_count": 10 + }, + "profile": { + "path": "tabs_raw/profile.md", + "partial": false, + "record_count": 8179 + }, + "logs": { + "path": "tabs_raw/logs.md", + "partial": true, + "record_count": 143 + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "partial": false, + "record_count": 4681 + } + }, + "note": "Generated from JoinQuant research get_backtest() schema v3 bundle. Platform logs are not provided by get_backtest().", + "integrity_status": "complete", + "integrity_issues": [], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 4681 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/api_export.json new file mode 100644 index 00000000..4b10c24c --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/api_export.json.gz", + "original_path": "api_export.json", + "original_sha256": "2bbf6cebbecd26e21ac1f1006b027866d01999577e29080f3cee09dc20a62478", + "compressed_sha256": "41b6846050158d62ea66c163542735e8d5da246f99f8a47d98dbd53f7eab4a92", + "original_bytes": 8085596 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/detail_api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/detail_api_export.json new file mode 100644 index 00000000..65e06db9 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/detail_api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/detail_api_export.json.gz", + "original_path": "detail_api_export.json", + "original_sha256": "1dab601403be7c65c47edd17a93fde95c08ec7f9217a018c826e6d6f13e3b5e9", + "compressed_sha256": "08da94376bf4d659b4021d34f4fcbbebcde6ec36dbf5f6ba69ad81156d823c6a", + "original_bytes": 4322026 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/integrity.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/integrity.json new file mode 100644 index 00000000..a7cc206d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/integrity.json @@ -0,0 +1,153 @@ +{ + "status": "complete", + "generated_at": "2026-06-30T16:38:34.562603+00:00", + "issues": [], + "checks": [ + { + "name": "research_bundle", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": null, + "partial": null, + "message": "research get_backtest bundle is required" + }, + { + "name": "detail_api_bundle", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": null, + "partial": null, + "message": "detail API bundle is required" + }, + { + "name": "metadata", + "required": true, + "status": "pass", + "source": "metadata.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "summary_metrics", + "required": true, + "status": "pass", + "source": "summary_metrics.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "research_results", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 1289, + "partial": false, + "message": "" + }, + { + "name": "research_positions", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 2386, + "partial": false, + "message": "" + }, + { + "name": "research_orders", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 378, + "partial": false, + "message": "" + }, + { + "name": "research_risk", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 31, + "partial": false, + "message": "" + }, + { + "name": "detail_results", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 1485, + "partial": false, + "message": "" + }, + { + "name": "detail_transactions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 378, + "partial": false, + "message": "" + }, + { + "name": "detail_positions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 3675, + "partial": false, + "message": "" + }, + { + "name": "detail_risk_tabs", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 640, + "partial": null, + "message": "" + }, + { + "name": "platform_logs", + "required": false, + "status": "warn", + "source": "detail_api_export.json", + "count": 1000, + "partial": true, + "message": "platform logs may be partial; audit_log is canonical" + }, + { + "name": "audit_log", + "required": true, + "status": "pass", + "source": "tabs_raw/audit_log.jsonl", + "count": 4681, + "partial": false, + "message": "" + } + ], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 4681 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/metadata.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/metadata.json new file mode 100644 index 00000000..3da24d09 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/metadata.json @@ -0,0 +1,26 @@ +{ + "strategy_name": "etf_factor_rotation", + "run_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "dcc6252aadef8bb09592538da4dde54c", + "joinquant_backtest_id": "dcc6252aadef8bb09592538da4dde54c", + "backtest_url": "", + "generated_at": "2026-07-01T00:38:20", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_dcc6252aadef8bb09592538da4dde54c.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "8976d3093b13233b86b7a4c7f33c915e", + "audit_token": "etf_factor_rotation-s04-weekend-close-signal-next-open-variant-v2-20260701003223-e458457b", + "audit_path": "jq_auto_audit/etf_factor_rotation-s04-weekend-close-signal-next-open-variant-v2-20260701003223-e458457b.jsonl" +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/backtest_report.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/backtest_report.md new file mode 100644 index 00000000..2960b486 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/backtest_report.md @@ -0,0 +1,32 @@ +# 回测数据汇总 + +- 策略名称:etf_factor_rotation +- 回测 ID:dcc6252aadef8bb09592538da4dde54c +- 区间:2021-01-01 至 2026-04-30 +- 提取方式:聚宽研究环境 get_backtest();详情页接口仅作补充。 + +## 核心指标 + +| 指标 | 值 | +| --- | --- | +| 策略收益 | 127.57% | +| 策略年化收益 | 17.29% | +| 基准收益 | -7.75% | +| 超额收益 | 146.70% | +| 最大回撤 | 7.58% | +| 夏普比率 | 1.591 | +| 阿尔法 | 0.141 | +| 贝塔 | 0.141 | +| 信息比率 | 1.089 | + +## 数据覆盖 + +| 数据 | 记录数 | 完整度 | +| --- | ---: | --- | +| 每日收益 | 1289 | 完整 | +| 每日持仓&收益 | 2386 | 完整 | +| 订单/交易 | 378 | 完整 | +| record 记录 | 0 | 完整 | +| 每日账户市值 | 1289 | 完整 | +| 分期风险 | 10 | 完整 | +| 平台日志 | | 详情页接口部分 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/data-integrity.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/data-integrity.md new file mode 100644 index 00000000..53571070 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/report/data-integrity.md @@ -0,0 +1,26 @@ +# 数据完整性报告 + +- 状态:complete +- 生成时间:2026-06-30T16:38:34.562603+00:00 + +## 问题 +- 无 + +## 检查项 + +| 检查项 | 必须 | 状态 | 来源 | 记录数 | partial | 说明 | +| --- | --- | --- | --- | ---: | --- | --- | +| research_bundle | 是 | pass | api_export.json | | | research get_backtest bundle is required | +| detail_api_bundle | 是 | pass | detail_api_export.json | | | detail API bundle is required | +| metadata | 是 | pass | metadata.json | | | | +| summary_metrics | 是 | pass | summary_metrics.json | | | | +| research_results | 是 | pass | api_export.json | 1289 | False | | +| research_positions | 是 | pass | api_export.json | 2386 | False | | +| research_orders | 是 | pass | api_export.json | 378 | False | | +| research_risk | 是 | pass | api_export.json | 31 | False | | +| detail_results | 是 | pass | detail_api_export.json | 1485 | False | | +| detail_transactions | 是 | pass | detail_api_export.json | 378 | False | | +| detail_positions | 是 | pass | detail_api_export.json | 3675 | False | | +| detail_risk_tabs | 是 | pass | detail_api_export.json | 640 | | | +| platform_logs | 否 | warn | detail_api_export.json | 1000 | True | platform logs may be partial; audit_log is canonical | +| audit_log | 是 | pass | tabs_raw/audit_log.jsonl | 4681 | False | | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/summary_metrics.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/summary_metrics.json new file mode 100644 index 00000000..9b1f51da --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/summary_metrics.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/summary_metrics.json.gz", + "original_path": "summary_metrics.json", + "original_sha256": "317b30d9a2b2f3f72bb2dc7210bde94ef9e389e7939d8423c2b1f5ee5df3e21b", + "compressed_sha256": "135fb1ab5e2fa8cf0bcebadf03780f0ed8d55c488b02372a3b7ccbd393629cc0", + "original_bytes": 520 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/audit_log.jsonl b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/audit_log.jsonl new file mode 100644 index 00000000..27ca1ed1 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/audit_log.jsonl @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/audit_log.jsonl.gz", + "original_path": "tabs_raw/audit_log.jsonl", + "original_sha256": "ceb62476e6ca432598fe3ea267b3158c921375bbb1150672643fd52ba582753a", + "compressed_sha256": "49e9ee759d2e965c26d7779e4578024476bdc9a6ad171355f56822f868b46a8d", + "original_bytes": 5709740 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/balances.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/balances.md new file mode 100644 index 00000000..fb5770b8 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/balances.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/balances.md.gz", + "original_path": "tabs_raw/balances.md", + "original_sha256": "7f0d0c1bee6b6572516b623634b2a9d2169fea08f48e8a6aa033984cc1b09872", + "compressed_sha256": "aff79e04917b5b162bf6cd1545dbda15100863f97ce46b5a647459a33804fa59", + "original_bytes": 90521 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/daily_returns.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/daily_returns.md new file mode 100644 index 00000000..0dc918e9 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/daily_returns.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/daily_returns.md.gz", + "original_path": "tabs_raw/daily_returns.md", + "original_sha256": "fae7f563d4decde5e0f5e2708b6502e4e712b42cb2ce35e8817779cc40060a50", + "compressed_sha256": "8e31e63bf90b2d042d4f045a290db99eb6f3a0892a34462ee3caea975395f468", + "original_bytes": 92691 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/logs.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/logs.md new file mode 100644 index 00000000..0162a32d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/logs.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/logs.md.gz", + "original_path": "tabs_raw/logs.md", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "da2d0526c6e8ab4e76a9654f7b2dbb3cb3dea37e4f30cedb17eac65b7e92368c", + "original_bytes": 290 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/period_risks.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/period_risks.md new file mode 100644 index 00000000..122383d1 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/period_risks.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/period_risks.md.gz", + "original_path": "tabs_raw/period_risks.md", + "original_sha256": "661b2d392c210b2a0a17c1b5c5bfdf78e23dd67d577f87e008166997053e106c", + "compressed_sha256": "cb4afc7f1a21bf684ce07caa9ddb45476fe0733f6e048c4a5be76cc3fdb923a2", + "original_bytes": 62872 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/positioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/positioninfo.md new file mode 100644 index 00000000..434b6cf7 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/positioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/positioninfo.md.gz", + "original_path": "tabs_raw/positioninfo.md", + "original_sha256": "b66559bf2f878363ba35fa31fa8fcb6e0ad2f6c7249c723448db04164ba99e6b", + "compressed_sha256": "2e8ffe3c2f7a5df3669a203e25084a9e1974f5a5c0a4e6ba18fc817b35ee87da", + "original_bytes": 378767 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/profile.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/profile.md new file mode 100644 index 00000000..59be9c2b --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/profile.md @@ -0,0 +1,541 @@ +# 性能分析 + +## fund_code + +- 总耗时:0.044457s + +```text +Total time: 0.044457 s +File: /tmp/strategy/user_code.py +Function: fund_code at line 129 +``` + +## format_etf_name + +- 总耗时:0.293356s + +```text +Total time: 0.293356 s +File: /tmp/strategy/user_code.py +Function: format_etf_name at line 131 +``` + +## build_etf_display_names + +- 总耗时:0.57098s + +```text +Total time: 0.57098 s +File: /tmp/strategy/user_code.py +Function: build_etf_display_names at line 144 +``` + +## fetch_etf_official_name + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: fetch_etf_official_name at line 151 +``` + +## load_etf_display_names + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: load_etf_display_names at line 161 +``` + +## _audit_jsonable + +- 总耗时:0.906061s + +```text +Total time: 0.906061 s +File: /tmp/strategy/user_code.py +Function: _audit_jsonable at line 169 +``` + +## _context_time_fields + +- 总耗时:0.665808s + +```text +Total time: 0.665808 s +File: /tmp/strategy/user_code.py +Function: _context_time_fields at line 198 +``` + +## audit_event + +- 总耗时:66.6509s + +```text +Total time: 66.6509 s +File: /tmp/strategy/user_code.py +Function: audit_event at line 205 +``` + +## _copy_runtime_default + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _copy_runtime_default at line 219 +``` + +## _runtime_param + +- 总耗时:0.169145s + +```text +Total time: 0.169145 s +File: /tmp/strategy/user_code.py +Function: _runtime_param at line 227 +``` + +## _runtime_list_param + +- 总耗时:0.07164s + +```text +Total time: 0.07164 s +File: /tmp/strategy/user_code.py +Function: _runtime_list_param at line 231 +``` + +## snapshot_params + +- 总耗时:0.847107s + +```text +Total time: 0.847107 s +File: /tmp/strategy/user_code.py +Function: snapshot_params at line 236 +``` + +## validate_params + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: validate_params at line 326 +``` + +## resolve_ma_long_windows + +- 总耗时:0.006776s + +```text +Total time: 0.006776 s +File: /tmp/strategy/user_code.py +Function: resolve_ma_long_windows at line 414 +``` + +## resolve_crowd_thresholds + +- 总耗时:0.012223s + +```text +Total time: 0.012223 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_thresholds at line 419 +``` + +## resolve_crowd_ret_windows + +- 总耗时:0.006518s + +```text +Total time: 0.006518 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_ret_windows at line 428 +``` + +## initialize + +- 总耗时:0.043361s + +```text +Total time: 0.043361 s +File: /tmp/strategy/user_code.py +Function: initialize at line 435 +``` + +## set_parameter + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: set_parameter at line 503 +``` + +## _log_step + +- 总耗时:3.36736s + +```text +Total time: 3.36736 s +File: /tmp/strategy/user_code.py +Function: _log_step at line 572 +``` + +## compose_raw_weights + +- 总耗时:0.016803s + +```text +Total time: 0.016803 s +File: /tmp/strategy/user_code.py +Function: compose_raw_weights at line 578 +``` + +## _as_date + +- 总耗时:0.02396s + +```text +Total time: 0.02396 s +File: /tmp/strategy/user_code.py +Function: _as_date at line 588 +``` + +## _context_trade_date + +- 总耗时:0.316054s + +```text +Total time: 0.316054 s +File: /tmp/strategy/user_code.py +Function: _context_trade_date at line 599 +``` + +## _same_iso_week + +- 总耗时:0.017228s + +```text +Total time: 0.017228 s +File: /tmp/strategy/user_code.py +Function: _same_iso_week at line 601 +``` + +## build_rebalance_plan + +- 总耗时:234.817s + +```text +Total time: 234.817 s +File: /tmp/strategy/user_code.py +Function: build_rebalance_plan at line 607 +``` + +## weekly_check + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: weekly_check at line 676 +``` + +## prepare_delay_only_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: prepare_delay_only_rebalance at line 679 +``` + +## execute_pending_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_pending_rebalance at line 690 +``` + +## mark_live_like_signal_day + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: mark_live_like_signal_day at line 718 +``` + +## execute_live_like_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_live_like_rebalance at line 727 +``` + +## prepare_weekend_close_rebalance + +- 总耗时:252.025s + +```text +Total time: 252.025 s +File: /tmp/strategy/user_code.py +Function: prepare_weekend_close_rebalance at line 765 +``` + +## execute_weekend_close_rebalance + +- 总耗时:30.2285s + +```text +Total time: 30.2285 s +File: /tmp/strategy/user_code.py +Function: execute_weekend_close_rebalance at line 791 +``` + +## normalize_field_frame + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: normalize_field_frame at line 832 +``` + +## compute_history_count + +- 总耗时:0.015492s + +```text +Total time: 0.015492 s +File: /tmp/strategy/user_code.py +Function: compute_history_count at line 841 +``` + +## fetch_field + +- 总耗时:110.149s + +```text +Total time: 110.149 s +File: /tmp/strategy/user_code.py +Function: fetch_field at line 851 +``` + +## get_history_data + +- 总耗时:113.442s + +```text +Total time: 113.442 s +File: /tmp/strategy/user_code.py +Function: get_history_data at line 871 +``` + +## compute_trend_gates + +- 总耗时:2.97831s + +```text +Total time: 2.97831 s +File: /tmp/strategy/user_code.py +Function: compute_trend_gates at line 884 +``` + +## compute_momentum_scores + +- 总耗时:7.50479s + +```text +Total time: 7.50479 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_scores at line 900 +``` + +## select_topk + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: select_topk at line 926 +``` + +## compute_rp_weights + +- 总耗时:1.84971s + +```text +Total time: 1.84971 s +File: /tmp/strategy/user_code.py +Function: compute_rp_weights at line 935 +``` + +## compute_rsrs_multipliers + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_multipliers at line 964 +``` + +## compute_rsrs_adjusted_scores + +- 总耗时:39.7464s + +```text +Total time: 39.7464 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_adjusted_scores at line 973 +``` + +## compute_momentum_tilt_multipliers + +- 总耗时:0.11612s + +```text +Total time: 0.11612 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_tilt_multipliers at line 1016 +``` + +## compute_rsrs_tilt_multipliers + +- 总耗时:40.0151s + +```text +Total time: 40.0151 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_tilt_multipliers at line 1037 +``` + +## apply_relative_tilts + +- 总耗时:0.059453s + +```text +Total time: 0.059453 s +File: /tmp/strategy/user_code.py +Function: apply_relative_tilts at line 1054 +``` + +## compute_crowd_penalties + +- 总耗时:36.7677s + +```text +Total time: 36.7677 s +File: /tmp/strategy/user_code.py +Function: compute_crowd_penalties at line 1070 +``` + +## percentile_rank + +- 总耗时:8.98988s + +```text +Total time: 8.98988 s +File: /tmp/strategy/user_code.py +Function: percentile_rank at line 1138 +``` + +## compute_portfolio_vol_scale_detail + +- 总耗时:1.56437s + +```text +Total time: 1.56437 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale_detail at line 1143 +``` + +## compute_portfolio_vol_scale + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale at line 1174 +``` + +## compute_portfolio_vol_asset_scales + +- 总耗时:3.82152s + +```text +Total time: 3.82152 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_asset_scales at line 1177 +``` + +## apply_fixed_gold_vol_relief + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_fixed_gold_vol_relief at line 1194 +``` + +## apply_dynamic_marginal_vol_relief + +- 总耗时:2.12634s + +```text +Total time: 2.12634 s +File: /tmp/strategy/user_code.py +Function: apply_dynamic_marginal_vol_relief at line 1217 +``` + +## select_dynamic_marginal_relief_asset + +- 总耗时:2.07143s + +```text +Total time: 2.07143 s +File: /tmp/strategy/user_code.py +Function: select_dynamic_marginal_relief_asset at line 1238 +``` + +## apply_weight_constraints + +- 总耗时:0.040995s + +```text +Total time: 0.040995 s +File: /tmp/strategy/user_code.py +Function: apply_weight_constraints at line 1273 +``` + +## execute_rebalance + +- 总耗时:13.8405s + +```text +Total time: 13.8405 s +File: /tmp/strategy/user_code.py +Function: execute_rebalance at line 1288 +``` diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/records.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/records.md new file mode 100644 index 00000000..8043b956 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/records.md @@ -0,0 +1,3 @@ +# Record 记录 + +(无数据) diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/risk.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/risk.md new file mode 100644 index 00000000..8b40eebd --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/risk.md @@ -0,0 +1,37 @@ +# 风险指标 + +- 记录数:31 + +| metric | value | +| --- | --- | +| __version | 101 | +| algorithm_return | 1.2757260135000044 | +| algorithm_volatility | 0.08355847327292946 | +| alpha | 0.14071401978170212 | +| annual_algo_return | 0.1729054101184353 | +| annual_bm_return | -0.01552798313301329 | +| avg_excess_return | 0.0007606280372008427 | +| avg_position_days | 795.3333333333334 | +| avg_trade_return | 0.0504205778963544 | +| benchmark_return | -0.07752074822165167 | +| benchmark_volatility | 0.17880949192018716 | +| beta | 0.1406247665174847 | +| day_win_ratio | 0.5407292474786657 | +| excess_return | 1.466967153042067 | +| excess_return_max_drawdown | 0.2138423055497961 | +| excess_return_max_drawdown_period | ['2024-09-13', '2024-10-08'] | +| excess_return_sharpe | 0.8745209669226747 | +| information | 1.0886011443852281 | +| lose_count | 51 | +| max_drawdown | 0.07577864706331894 | +| max_drawdown_period | ['2021-11-22', '2022-01-28'] | +| max_leverage | 0.0 | +| period_label | 2026-04 | +| profit_loss_ratio | 5.008322011146711 | +| sharpe | 1.5905677175829014 | +| sortino | 2.351794463723918 | +| trading_days | 1289 | +| treasury_return | 0.21282191780821919 | +| turnover_rate | 0.020487745186870018 | +| win_count | 140 | +| win_ratio | 0.7329842931937173 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/transactioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/transactioninfo.md new file mode 100644 index 00000000..3299236c --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c/tabs_raw/transactioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0032-btdcc6252aadef8bb09592538da4dde54c", + "dataset_file": "raw/transactioninfo.md.gz", + "original_path": "tabs_raw/transactioninfo.md", + "original_sha256": "9231003f977b453cf45f5064d02865af7d04a533f92034c9407db117c9e27c8d", + "compressed_sha256": "453d227ceed56221df0fdae0b17a53ebd4ddfd766a6f42cac90eb4d2f46cd011", + "original_bytes": 31888 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/all_data.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/all_data.json new file mode 100644 index 00000000..da026f50 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/all_data.json @@ -0,0 +1,105 @@ +{ + "api_export_json": "D:\\My Project\\Quant Trading\\strategies\\etf_factor_rotation\\backtest_runs\\20260701-0246-bt526c312fb836d6e07826ce170809486a\\api_export.json", + "extraction_method": "research_bundle", + "generated_at": "2026-06-30T18:50:06.737318+00:00", + "counts": { + "results": 1289, + "positions": 2386, + "orders": 378, + "records": 0, + "balances": 1289, + "period_risk_tabs": 10, + "audit_log_lines": 1630 + }, + "partial": { + "results": false, + "positions": false, + "orders": false, + "records": false, + "risk": false, + "period_risks": false, + "balances": false, + "logs": true, + "audit_log": false + }, + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_526c312fb836d6e07826ce170809486a.json", + "research_downloaded": true, + "detail_api_used": true, + "tabs": { + "daily_returns": { + "path": "tabs_raw/daily_returns.md", + "partial": false, + "record_count": 1289 + }, + "transactioninfo": { + "path": "tabs_raw/transactioninfo.md", + "partial": false, + "record_count": 378 + }, + "positioninfo": { + "path": "tabs_raw/positioninfo.md", + "partial": false, + "record_count": 2386 + }, + "records": { + "path": "tabs_raw/records.md", + "partial": false, + "record_count": 0 + }, + "balances": { + "path": "tabs_raw/balances.md", + "partial": false, + "record_count": 1289 + }, + "risk": { + "path": "tabs_raw/risk.md", + "partial": false, + "record_count": 31 + }, + "period_risks": { + "path": "tabs_raw/period_risks.md", + "partial": false, + "record_count": 10 + }, + "profile": { + "path": "tabs_raw/profile.md", + "partial": false, + "record_count": 9131 + }, + "logs": { + "path": "tabs_raw/logs.md", + "partial": true, + "record_count": 143 + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "partial": false, + "record_count": 1630 + } + }, + "note": "Generated from JoinQuant research get_backtest() schema v3 bundle. Platform logs are not provided by get_backtest().", + "integrity_status": "complete", + "integrity_issues": [], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1630 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/api_export.json new file mode 100644 index 00000000..acf38afc --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/api_export.json.gz", + "original_path": "api_export.json", + "original_sha256": "5ca0d57eddc4d68c0666a648fa8ae947e7e245376b79a0bbe27889859a46166b", + "compressed_sha256": "0f220177b98c9168e238171dcd67d2fa097cf0246d03b49f5c890e88bc261704", + "original_bytes": 3827821 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/detail_api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/detail_api_export.json new file mode 100644 index 00000000..6e0f096f --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/detail_api_export.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/detail_api_export.json.gz", + "original_path": "detail_api_export.json", + "original_sha256": "12c70ee14b6d0ae1b37613abb337844620a44a3ca3261e301f7f2de6a54b1eff", + "compressed_sha256": "7fc77f963819736f60d350045885ab71908ed41be5a0a9c44e00ef4be7d74905", + "original_bytes": 4370080 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/integrity.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/integrity.json new file mode 100644 index 00000000..2653b7cc --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/integrity.json @@ -0,0 +1,153 @@ +{ + "status": "complete", + "generated_at": "2026-06-30T18:50:06.780422+00:00", + "issues": [], + "checks": [ + { + "name": "research_bundle", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": null, + "partial": null, + "message": "research get_backtest bundle is required" + }, + { + "name": "detail_api_bundle", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": null, + "partial": null, + "message": "detail API bundle is required" + }, + { + "name": "metadata", + "required": true, + "status": "pass", + "source": "metadata.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "summary_metrics", + "required": true, + "status": "pass", + "source": "summary_metrics.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "research_results", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 1289, + "partial": false, + "message": "" + }, + { + "name": "research_positions", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 2386, + "partial": false, + "message": "" + }, + { + "name": "research_orders", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 378, + "partial": false, + "message": "" + }, + { + "name": "research_risk", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 31, + "partial": false, + "message": "" + }, + { + "name": "detail_results", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 1485, + "partial": false, + "message": "" + }, + { + "name": "detail_transactions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 378, + "partial": false, + "message": "" + }, + { + "name": "detail_positions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 3675, + "partial": false, + "message": "" + }, + { + "name": "detail_risk_tabs", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 640, + "partial": null, + "message": "" + }, + { + "name": "platform_logs", + "required": false, + "status": "warn", + "source": "detail_api_export.json", + "count": 1000, + "partial": true, + "message": "platform logs may be partial; audit_log is canonical" + }, + { + "name": "audit_log", + "required": true, + "status": "pass", + "source": "tabs_raw/audit_log.jsonl", + "count": 1630, + "partial": false, + "message": "" + } + ], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1630 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/metadata.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/metadata.json new file mode 100644 index 00000000..8e194938 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/metadata.json @@ -0,0 +1,26 @@ +{ + "strategy_name": "etf_factor_rotation", + "run_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "strategy_file": "", + "strategy_dir": "", + "start_date_requested": "", + "start_date_effective": "2021-01-01", + "end_date_requested": "", + "end_date_effective": "2026-04-30", + "capital": 100000, + "backtest_id": "526c312fb836d6e07826ce170809486a", + "joinquant_backtest_id": "526c312fb836d6e07826ce170809486a", + "backtest_url": "", + "generated_at": "2026-07-01T02:49:55", + "extraction_method": "joinquant_research_get_backtest", + "primary_extraction_method": "joinquant_research_get_backtest", + "fallback_extraction_method": "", + "research_export_path": "jq_auto_exports/research_backtest_526c312fb836d6e07826ce170809486a.json", + "research_downloaded": true, + "detail_api_used": true, + "frequency": "1d", + "py_version": "Python3", + "internal_backtest_id": "b6cb7f66e1fd859bcf372f0813ab5d75", + "audit_token": "etf_factor_rotation-s04-weekend-close-signal-next-open-variant-v2-20260701024558-124bb940", + "audit_path": "jq_auto_audit/etf_factor_rotation-s04-weekend-close-signal-next-open-variant-v2-20260701024558-124bb940.jsonl" +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/backtest_report.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/backtest_report.md new file mode 100644 index 00000000..ee6e37ed --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/backtest_report.md @@ -0,0 +1,32 @@ +# 回测数据汇总 + +- 策略名称:etf_factor_rotation +- 回测 ID:526c312fb836d6e07826ce170809486a +- 区间:2021-01-01 至 2026-04-30 +- 提取方式:聚宽研究环境 get_backtest();详情页接口仅作补充。 + +## 核心指标 + +| 指标 | 值 | +| --- | --- | +| 策略收益 | 127.57% | +| 策略年化收益 | 17.29% | +| 基准收益 | -7.75% | +| 超额收益 | 146.70% | +| 最大回撤 | 7.58% | +| 夏普比率 | 1.591 | +| 阿尔法 | 0.141 | +| 贝塔 | 0.141 | +| 信息比率 | 1.089 | + +## 数据覆盖 + +| 数据 | 记录数 | 完整度 | +| --- | ---: | --- | +| 每日收益 | 1289 | 完整 | +| 每日持仓&收益 | 2386 | 完整 | +| 订单/交易 | 378 | 完整 | +| record 记录 | 0 | 完整 | +| 每日账户市值 | 1289 | 完整 | +| 分期风险 | 10 | 完整 | +| 平台日志 | | 详情页接口部分 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/data-integrity.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/data-integrity.md new file mode 100644 index 00000000..ddf34b60 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/report/data-integrity.md @@ -0,0 +1,26 @@ +# 数据完整性报告 + +- 状态:complete +- 生成时间:2026-06-30T18:50:06.780422+00:00 + +## 问题 +- 无 + +## 检查项 + +| 检查项 | 必须 | 状态 | 来源 | 记录数 | partial | 说明 | +| --- | --- | --- | --- | ---: | --- | --- | +| research_bundle | 是 | pass | api_export.json | | | research get_backtest bundle is required | +| detail_api_bundle | 是 | pass | detail_api_export.json | | | detail API bundle is required | +| metadata | 是 | pass | metadata.json | | | | +| summary_metrics | 是 | pass | summary_metrics.json | | | | +| research_results | 是 | pass | api_export.json | 1289 | False | | +| research_positions | 是 | pass | api_export.json | 2386 | False | | +| research_orders | 是 | pass | api_export.json | 378 | False | | +| research_risk | 是 | pass | api_export.json | 31 | False | | +| detail_results | 是 | pass | detail_api_export.json | 1485 | False | | +| detail_transactions | 是 | pass | detail_api_export.json | 378 | False | | +| detail_positions | 是 | pass | detail_api_export.json | 3675 | False | | +| detail_risk_tabs | 是 | pass | detail_api_export.json | 640 | | | +| platform_logs | 否 | warn | detail_api_export.json | 1000 | True | platform logs may be partial; audit_log is canonical | +| audit_log | 是 | pass | tabs_raw/audit_log.jsonl | 1630 | False | | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/summary_metrics.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/summary_metrics.json new file mode 100644 index 00000000..7ca89e31 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/summary_metrics.json @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/summary_metrics.json.gz", + "original_path": "summary_metrics.json", + "original_sha256": "317b30d9a2b2f3f72bb2dc7210bde94ef9e389e7939d8423c2b1f5ee5df3e21b", + "compressed_sha256": "135fb1ab5e2fa8cf0bcebadf03780f0ed8d55c488b02372a3b7ccbd393629cc0", + "original_bytes": 520 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/audit_log.jsonl b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/audit_log.jsonl new file mode 100644 index 00000000..b54363ee --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/audit_log.jsonl @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/audit_log.jsonl.gz", + "original_path": "tabs_raw/audit_log.jsonl", + "original_sha256": "b673ad3c49c5782fee85e5d062b55ff081a029eb3f58dda4d87ac2dc2b28525b", + "compressed_sha256": "2dd4f6c4a4b5c439e14dd8d30560557c2788de087cd8b7864ad0f22b03b9210c", + "original_bytes": 1742407 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/balances.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/balances.md new file mode 100644 index 00000000..d85b3cec --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/balances.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/balances.md.gz", + "original_path": "tabs_raw/balances.md", + "original_sha256": "7f0d0c1bee6b6572516b623634b2a9d2169fea08f48e8a6aa033984cc1b09872", + "compressed_sha256": "aff79e04917b5b162bf6cd1545dbda15100863f97ce46b5a647459a33804fa59", + "original_bytes": 90521 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/daily_returns.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/daily_returns.md new file mode 100644 index 00000000..a36e8952 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/daily_returns.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/daily_returns.md.gz", + "original_path": "tabs_raw/daily_returns.md", + "original_sha256": "fae7f563d4decde5e0f5e2708b6502e4e712b42cb2ce35e8817779cc40060a50", + "compressed_sha256": "8e31e63bf90b2d042d4f045a290db99eb6f3a0892a34462ee3caea975395f468", + "original_bytes": 92691 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/logs.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/logs.md new file mode 100644 index 00000000..1af1c61c --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/logs.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/logs.md.gz", + "original_path": "tabs_raw/logs.md", + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + "compressed_sha256": "da2d0526c6e8ab4e76a9654f7b2dbb3cb3dea37e4f30cedb17eac65b7e92368c", + "original_bytes": 290 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/period_risks.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/period_risks.md new file mode 100644 index 00000000..c99ad2ec --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/period_risks.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/period_risks.md.gz", + "original_path": "tabs_raw/period_risks.md", + "original_sha256": "661b2d392c210b2a0a17c1b5c5bfdf78e23dd67d577f87e008166997053e106c", + "compressed_sha256": "cb4afc7f1a21bf684ce07caa9ddb45476fe0733f6e048c4a5be76cc3fdb923a2", + "original_bytes": 62872 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/positioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/positioninfo.md new file mode 100644 index 00000000..57ab0ab5 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/positioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/positioninfo.md.gz", + "original_path": "tabs_raw/positioninfo.md", + "original_sha256": "b66559bf2f878363ba35fa31fa8fcb6e0ad2f6c7249c723448db04164ba99e6b", + "compressed_sha256": "2e8ffe3c2f7a5df3669a203e25084a9e1974f5a5c0a4e6ba18fc817b35ee87da", + "original_bytes": 378767 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/profile.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/profile.md new file mode 100644 index 00000000..45b31840 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/profile.md @@ -0,0 +1,601 @@ +# 性能分析 + +## fund_code + +- 总耗时:0.010966s + +```text +Total time: 0.010966 s +File: /tmp/strategy/user_code.py +Function: fund_code at line 129 +``` + +## format_etf_name + +- 总耗时:0.069979s + +```text +Total time: 0.069979 s +File: /tmp/strategy/user_code.py +Function: format_etf_name at line 131 +``` + +## build_etf_display_names + +- 总耗时:0.135742s + +```text +Total time: 0.135742 s +File: /tmp/strategy/user_code.py +Function: build_etf_display_names at line 144 +``` + +## fetch_etf_official_name + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: fetch_etf_official_name at line 151 +``` + +## load_etf_display_names + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: load_etf_display_names at line 161 +``` + +## _audit_jsonable + +- 总耗时:0.292948s + +```text +Total time: 0.292948 s +File: /tmp/strategy/user_code.py +Function: _audit_jsonable at line 169 +``` + +## _context_time_fields + +- 总耗时:0.273677s + +```text +Total time: 0.273677 s +File: /tmp/strategy/user_code.py +Function: _context_time_fields at line 198 +``` + +## audit_event + +- 总耗时:27.3032s + +```text +Total time: 27.3032 s +File: /tmp/strategy/user_code.py +Function: audit_event at line 205 +``` + +## _copy_runtime_default + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _copy_runtime_default at line 219 +``` + +## _runtime_param + +- 总耗时:0.036228s + +```text +Total time: 0.036228 s +File: /tmp/strategy/user_code.py +Function: _runtime_param at line 227 +``` + +## _runtime_list_param + +- 总耗时:0.015368s + +```text +Total time: 0.015368 s +File: /tmp/strategy/user_code.py +Function: _runtime_list_param at line 231 +``` + +## snapshot_params + +- 总耗时:0.178768s + +```text +Total time: 0.178768 s +File: /tmp/strategy/user_code.py +Function: snapshot_params at line 236 +``` + +## validate_params + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: validate_params at line 326 +``` + +## resolve_ma_long_windows + +- 总耗时:0.001585s + +```text +Total time: 0.001585 s +File: /tmp/strategy/user_code.py +Function: resolve_ma_long_windows at line 414 +``` + +## resolve_crowd_thresholds + +- 总耗时:0.002929s + +```text +Total time: 0.002929 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_thresholds at line 419 +``` + +## resolve_crowd_ret_windows + +- 总耗时:0.001343s + +```text +Total time: 0.001343 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_ret_windows at line 428 +``` + +## initialize + +- 总耗时:0.039548s + +```text +Total time: 0.039548 s +File: /tmp/strategy/user_code.py +Function: initialize at line 435 +``` + +## set_parameter + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: set_parameter at line 505 +``` + +## _log_step + +- 总耗时:0.756459s + +```text +Total time: 0.756459 s +File: /tmp/strategy/user_code.py +Function: _log_step at line 574 +``` + +## compose_raw_weights + +- 总耗时:0.00355s + +```text +Total time: 0.00355 s +File: /tmp/strategy/user_code.py +Function: compose_raw_weights at line 580 +``` + +## _as_date + +- 总耗时:0.005303s + +```text +Total time: 0.005303 s +File: /tmp/strategy/user_code.py +Function: _as_date at line 590 +``` + +## _context_trade_date + +- 总耗时:0.079189s + +```text +Total time: 0.079189 s +File: /tmp/strategy/user_code.py +Function: _context_trade_date at line 601 +``` + +## _same_iso_week + +- 总耗时:0.003889s + +```text +Total time: 0.003889 s +File: /tmp/strategy/user_code.py +Function: _same_iso_week at line 603 +``` + +## _float_list + +- 总耗时:0.002569s + +```text +Total time: 0.002569 s +File: /tmp/strategy/user_code.py +Function: _float_list at line 609 +``` + +## _weekend_close_batch_id + +- 总耗时:0.00255s + +```text +Total time: 0.00255 s +File: /tmp/strategy/user_code.py +Function: _weekend_close_batch_id at line 616 +``` + +## _enrich_weekend_close_plan + +- 总耗时:0.186498s + +```text +Total time: 0.186498 s +File: /tmp/strategy/user_code.py +Function: _enrich_weekend_close_plan at line 619 +``` + +## _report_signal_plan + +- 总耗时:79.9831s + +```text +Total time: 79.9831 s +File: /tmp/strategy/user_code.py +Function: _report_signal_plan at line 635 +``` + +## _suppress_execution_notice + +- 总耗时:0.002315s + +```text +Total time: 0.002315 s +File: /tmp/strategy/user_code.py +Function: _suppress_execution_notice at line 646 +``` + +## _resume_execution_notice + +- 总耗时:0.002269s + +```text +Total time: 0.002269 s +File: /tmp/strategy/user_code.py +Function: _resume_execution_notice at line 658 +``` + +## build_rebalance_plan + +- 总耗时:51.4301s + +```text +Total time: 51.4301 s +File: /tmp/strategy/user_code.py +Function: build_rebalance_plan at line 668 +``` + +## weekly_check + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: weekly_check at line 737 +``` + +## prepare_delay_only_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: prepare_delay_only_rebalance at line 740 +``` + +## execute_pending_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_pending_rebalance at line 751 +``` + +## mark_live_like_signal_day + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: mark_live_like_signal_day at line 779 +``` + +## execute_live_like_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_live_like_rebalance at line 788 +``` + +## prepare_weekend_close_rebalance + +- 总耗时:136.37s + +```text +Total time: 136.37 s +File: /tmp/strategy/user_code.py +Function: prepare_weekend_close_rebalance at line 826 +``` + +## execute_weekend_close_rebalance + +- 总耗时:19.7858s + +```text +Total time: 19.7858 s +File: /tmp/strategy/user_code.py +Function: execute_weekend_close_rebalance at line 855 +``` + +## normalize_field_frame + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: normalize_field_frame at line 906 +``` + +## compute_history_count + +- 总耗时:0.003345s + +```text +Total time: 0.003345 s +File: /tmp/strategy/user_code.py +Function: compute_history_count at line 915 +``` + +## fetch_field + +- 总耗时:24.0671s + +```text +Total time: 24.0671 s +File: /tmp/strategy/user_code.py +Function: fetch_field at line 925 +``` + +## get_history_data + +- 总耗时:24.8426s + +```text +Total time: 24.8426 s +File: /tmp/strategy/user_code.py +Function: get_history_data at line 945 +``` + +## compute_trend_gates + +- 总耗时:0.64189s + +```text +Total time: 0.64189 s +File: /tmp/strategy/user_code.py +Function: compute_trend_gates at line 958 +``` + +## compute_momentum_scores + +- 总耗时:1.58835s + +```text +Total time: 1.58835 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_scores at line 974 +``` + +## select_topk + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: select_topk at line 1000 +``` + +## compute_rp_weights + +- 总耗时:0.38362s + +```text +Total time: 0.38362 s +File: /tmp/strategy/user_code.py +Function: compute_rp_weights at line 1009 +``` + +## compute_rsrs_multipliers + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_multipliers at line 1038 +``` + +## compute_rsrs_adjusted_scores + +- 总耗时:8.67028s + +```text +Total time: 8.67028 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_adjusted_scores at line 1047 +``` + +## compute_momentum_tilt_multipliers + +- 总耗时:0.025009s + +```text +Total time: 0.025009 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_tilt_multipliers at line 1090 +``` + +## compute_rsrs_tilt_multipliers + +- 总耗时:8.7307s + +```text +Total time: 8.7307 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_tilt_multipliers at line 1111 +``` + +## apply_relative_tilts + +- 总耗时:0.012662s + +```text +Total time: 0.012662 s +File: /tmp/strategy/user_code.py +Function: apply_relative_tilts at line 1128 +``` + +## compute_crowd_penalties + +- 总耗时:7.88395s + +```text +Total time: 7.88395 s +File: /tmp/strategy/user_code.py +Function: compute_crowd_penalties at line 1144 +``` + +## percentile_rank + +- 总耗时:1.93572s + +```text +Total time: 1.93572 s +File: /tmp/strategy/user_code.py +Function: percentile_rank at line 1212 +``` + +## compute_portfolio_vol_scale_detail + +- 总耗时:0.31576s + +```text +Total time: 0.31576 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale_detail at line 1217 +``` + +## compute_portfolio_vol_scale + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale at line 1248 +``` + +## compute_portfolio_vol_asset_scales + +- 总耗时:0.804712s + +```text +Total time: 0.804712 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_asset_scales at line 1251 +``` + +## apply_fixed_gold_vol_relief + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_fixed_gold_vol_relief at line 1268 +``` + +## apply_dynamic_marginal_vol_relief + +- 总耗时:0.460663s + +```text +Total time: 0.460663 s +File: /tmp/strategy/user_code.py +Function: apply_dynamic_marginal_vol_relief at line 1291 +``` + +## select_dynamic_marginal_relief_asset + +- 总耗时:0.448597s + +```text +Total time: 0.448597 s +File: /tmp/strategy/user_code.py +Function: select_dynamic_marginal_relief_asset at line 1312 +``` + +## apply_weight_constraints + +- 总耗时:0.008117s + +```text +Total time: 0.008117 s +File: /tmp/strategy/user_code.py +Function: apply_weight_constraints at line 1347 +``` + +## execute_rebalance + +- 总耗时:15.5078s + +```text +Total time: 15.5078 s +File: /tmp/strategy/user_code.py +Function: execute_rebalance at line 1362 +``` diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/records.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/records.md new file mode 100644 index 00000000..8043b956 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/records.md @@ -0,0 +1,3 @@ +# Record 记录 + +(无数据) diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/risk.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/risk.md new file mode 100644 index 00000000..8b40eebd --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/risk.md @@ -0,0 +1,37 @@ +# 风险指标 + +- 记录数:31 + +| metric | value | +| --- | --- | +| __version | 101 | +| algorithm_return | 1.2757260135000044 | +| algorithm_volatility | 0.08355847327292946 | +| alpha | 0.14071401978170212 | +| annual_algo_return | 0.1729054101184353 | +| annual_bm_return | -0.01552798313301329 | +| avg_excess_return | 0.0007606280372008427 | +| avg_position_days | 795.3333333333334 | +| avg_trade_return | 0.0504205778963544 | +| benchmark_return | -0.07752074822165167 | +| benchmark_volatility | 0.17880949192018716 | +| beta | 0.1406247665174847 | +| day_win_ratio | 0.5407292474786657 | +| excess_return | 1.466967153042067 | +| excess_return_max_drawdown | 0.2138423055497961 | +| excess_return_max_drawdown_period | ['2024-09-13', '2024-10-08'] | +| excess_return_sharpe | 0.8745209669226747 | +| information | 1.0886011443852281 | +| lose_count | 51 | +| max_drawdown | 0.07577864706331894 | +| max_drawdown_period | ['2021-11-22', '2022-01-28'] | +| max_leverage | 0.0 | +| period_label | 2026-04 | +| profit_loss_ratio | 5.008322011146711 | +| sharpe | 1.5905677175829014 | +| sortino | 2.351794463723918 | +| trading_days | 1289 | +| treasury_return | 0.21282191780821919 | +| turnover_rate | 0.020487745186870018 | +| win_count | 140 | +| win_ratio | 0.7329842931937173 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/transactioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/transactioninfo.md new file mode 100644 index 00000000..bed5c08e --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a/tabs_raw/transactioninfo.md @@ -0,0 +1,11 @@ +{ + "kind": "data_center_pointer", + "dataset_id": "etf_factor_rotation_backtest_runs", + "snapshot_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0246-bt526c312fb836d6e07826ce170809486a", + "dataset_file": "raw/transactioninfo.md.gz", + "original_path": "tabs_raw/transactioninfo.md", + "original_sha256": "9231003f977b453cf45f5064d02865af7d04a533f92034c9407db117c9e27c8d", + "compressed_sha256": "453d227ceed56221df0fdae0b17a53ebd4ddfd766a6f42cac90eb4d2f46cd011", + "original_bytes": 31888 +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/all_data.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/all_data.json new file mode 100644 index 00000000..a516963e --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/all_data.json @@ -0,0 +1,209 @@ +{ + + "api_export_json": "D:\\My Project\\Quant Trading\\strategies\\etf_factor_rotation\\backtest_runs\\20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d\\api_export.json", + + "extraction_method": "research_bundle", + + "generated_at": "2026-06-30T19:37:11.537380+00:00", + + "counts": { + + "results": 1289, + + "positions": 2386, + + "orders": 378, + + "records": 0, + + "balances": 1289, + + "period_risk_tabs": 10, + + "audit_log_lines": 1630 + + }, + + "partial": { + + "results": false, + + "positions": false, + + "orders": false, + + "records": false, + + "risk": false, + + "period_risks": false, + + "balances": false, + + "logs": true, + + "audit_log": false + + }, + + "primary_extraction_method": "joinquant_research_get_backtest", + + "fallback_extraction_method": "", + + "research_export_path": "jq_auto_exports/research_backtest_bfc5066102fc7eac2f3dfd3da0276a3d.json", + + "research_downloaded": true, + + "detail_api_used": true, + + "tabs": { + + "daily_returns": { + + "path": "tabs_raw/daily_returns.md", + + "partial": false, + + "record_count": 1289 + + }, + + "transactioninfo": { + + "path": "tabs_raw/transactioninfo.md", + + "partial": false, + + "record_count": 378 + + }, + + "positioninfo": { + + "path": "tabs_raw/positioninfo.md", + + "partial": false, + + "record_count": 2386 + + }, + + "records": { + + "path": "tabs_raw/records.md", + + "partial": false, + + "record_count": 0 + + }, + + "balances": { + + "path": "tabs_raw/balances.md", + + "partial": false, + + "record_count": 1289 + + }, + + "risk": { + + "path": "tabs_raw/risk.md", + + "partial": false, + + "record_count": 31 + + }, + + "period_risks": { + + "path": "tabs_raw/period_risks.md", + + "partial": false, + + "record_count": 10 + + }, + + "profile": { + + "path": "tabs_raw/profile.md", + + "partial": false, + + "record_count": 9137 + + }, + + "logs": { + + "path": "tabs_raw/logs.md", + + "partial": true, + + "record_count": 143 + + }, + + "audit_log": { + + "path": "tabs_raw/audit_log.jsonl", + + "partial": false, + + "record_count": 1630 + + } + + }, + + "note": "Generated from JoinQuant research get_backtest() schema v3 bundle. Platform logs are not provided by get_backtest().", + + "integrity_status": "complete", + + "integrity_issues": [], + + "sources": { + + "research": { + + "path": "api_export.json", + + "present": true, + + "method": "joinquant_research_get_backtest" + + }, + + "detail_api": { + + "path": "detail_api_export.json", + + "present": true + + }, + + "audit_log": { + + "path": "tabs_raw/audit_log.jsonl", + + "present": true, + + "count": 1630 + + }, + + "platform_logs": { + + "path": "tabs_raw/logs.md", + + "partial": true + + } + + } + +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/api_export.json new file mode 100644 index 00000000..34d8316d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/api_export.json @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/api_export.json.gz", + + "original_path": "api_export.json", + + "original_sha256": "7c347eb465d800422c4f50a761c401156985f1330b3cb90d585b1bd697a89221", + + "compressed_sha256": "ed009f3dcbe7d329e092fa016fe83dc64c3754d1331fc399e6424e059c1f9726", + + "original_bytes": 3784618 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/detail_api_export.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/detail_api_export.json new file mode 100644 index 00000000..fa8d0096 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/detail_api_export.json @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/detail_api_export.json.gz", + + "original_path": "detail_api_export.json", + + "original_sha256": "3ccd53bb53f1e860ba5842f364cc4fec65245e29d8a0f04062d8023fb21047f4", + + "compressed_sha256": "333d4d723204b27a3588141bee9d6b8bf12bc17f2d71a2e855c6d3443d2791b1", + + "original_bytes": 4370413 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/integrity.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/integrity.json new file mode 100644 index 00000000..6bc75065 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/integrity.json @@ -0,0 +1,153 @@ +{ + "status": "complete", + "generated_at": "2026-06-30T19:37:11.606499+00:00", + "issues": [], + "checks": [ + { + "name": "research_bundle", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": null, + "partial": null, + "message": "research get_backtest bundle is required" + }, + { + "name": "detail_api_bundle", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": null, + "partial": null, + "message": "detail API bundle is required" + }, + { + "name": "metadata", + "required": true, + "status": "pass", + "source": "metadata.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "summary_metrics", + "required": true, + "status": "pass", + "source": "summary_metrics.json", + "count": null, + "partial": null, + "message": "" + }, + { + "name": "research_results", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 1289, + "partial": false, + "message": "" + }, + { + "name": "research_positions", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 2386, + "partial": false, + "message": "" + }, + { + "name": "research_orders", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 378, + "partial": false, + "message": "" + }, + { + "name": "research_risk", + "required": true, + "status": "pass", + "source": "api_export.json", + "count": 31, + "partial": false, + "message": "" + }, + { + "name": "detail_results", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 1485, + "partial": false, + "message": "" + }, + { + "name": "detail_transactions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 378, + "partial": false, + "message": "" + }, + { + "name": "detail_positions", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 3675, + "partial": false, + "message": "" + }, + { + "name": "detail_risk_tabs", + "required": true, + "status": "pass", + "source": "detail_api_export.json", + "count": 640, + "partial": null, + "message": "" + }, + { + "name": "platform_logs", + "required": false, + "status": "warn", + "source": "detail_api_export.json", + "count": 1000, + "partial": true, + "message": "platform logs may be partial; audit_log is canonical" + }, + { + "name": "audit_log", + "required": true, + "status": "pass", + "source": "tabs_raw/audit_log.jsonl", + "count": 1630, + "partial": false, + "message": "" + } + ], + "sources": { + "research": { + "path": "api_export.json", + "present": true, + "method": "joinquant_research_get_backtest" + }, + "detail_api": { + "path": "detail_api_export.json", + "present": true + }, + "audit_log": { + "path": "tabs_raw/audit_log.jsonl", + "present": true, + "count": 1630 + }, + "platform_logs": { + "path": "tabs_raw/logs.md", + "partial": true + } + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/metadata.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/metadata.json new file mode 100644 index 00000000..7d0d874d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/metadata.json @@ -0,0 +1,44 @@ +{ + + "strategy_name": "etf_factor_rotation", + "run_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + "strategy_file": "", + "strategy_dir": "", + + "start_date_requested": "", + + "start_date_effective": "2021-01-01", + + "end_date_requested": "", + + "end_date_effective": "2026-04-30", + + "capital": 100000, + "backtest_id": "bfc5066102fc7eac2f3dfd3da0276a3d", + "joinquant_backtest_id": "bfc5066102fc7eac2f3dfd3da0276a3d", + "backtest_url": "", + "generated_at": "2026-07-01T03:36:53", + + "extraction_method": "joinquant_research_get_backtest", + + "primary_extraction_method": "joinquant_research_get_backtest", + + "fallback_extraction_method": "", + + "research_export_path": "jq_auto_exports/research_backtest_bfc5066102fc7eac2f3dfd3da0276a3d.json", + + "research_downloaded": true, + + "detail_api_used": true, + + "frequency": "1d", + + "py_version": "Python3", + + "internal_backtest_id": "9f29ab61c433f227af01e2ed1cdcff85", + + "audit_token": "etf_factor_rotation-s05-official-current-baseline-20260701033128-41374eef", + + "audit_path": "jq_auto_audit/etf_factor_rotation-s05-official-current-baseline-20260701033128-41374eef.jsonl" + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/backtest_report.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/backtest_report.md new file mode 100644 index 00000000..ae9c2f9c --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/backtest_report.md @@ -0,0 +1,32 @@ +# 回测数据汇总 + +- 策略名称:etf_factor_rotation +- 回测 ID:bfc5066102fc7eac2f3dfd3da0276a3d +- 区间:2021-01-01 至 2026-04-30 +- 提取方式:聚宽研究环境 get_backtest();详情页接口仅作补充。 + +## 核心指标 + +| 指标 | 值 | +| --- | --- | +| 策略收益 | 127.57% | +| 策略年化收益 | 17.29% | +| 基准收益 | -7.75% | +| 超额收益 | 146.70% | +| 最大回撤 | 7.58% | +| 夏普比率 | 1.591 | +| 阿尔法 | 0.141 | +| 贝塔 | 0.141 | +| 信息比率 | 1.089 | + +## 数据覆盖 + +| 数据 | 记录数 | 完整度 | +| --- | ---: | --- | +| 每日收益 | 1289 | 完整 | +| 每日持仓&收益 | 2386 | 完整 | +| 订单/交易 | 378 | 完整 | +| record 记录 | 0 | 完整 | +| 每日账户市值 | 1289 | 完整 | +| 分期风险 | 10 | 完整 | +| 平台日志 | | 详情页接口部分 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/data-integrity.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/data-integrity.md new file mode 100644 index 00000000..a2faf395 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/report/data-integrity.md @@ -0,0 +1,26 @@ +# 数据完整性报告 + +- 状态:complete +- 生成时间:2026-06-30T19:37:11.606499+00:00 + +## 问题 +- 无 + +## 检查项 + +| 检查项 | 必须 | 状态 | 来源 | 记录数 | partial | 说明 | +| --- | --- | --- | --- | ---: | --- | --- | +| research_bundle | 是 | pass | api_export.json | | | research get_backtest bundle is required | +| detail_api_bundle | 是 | pass | detail_api_export.json | | | detail API bundle is required | +| metadata | 是 | pass | metadata.json | | | | +| summary_metrics | 是 | pass | summary_metrics.json | | | | +| research_results | 是 | pass | api_export.json | 1289 | False | | +| research_positions | 是 | pass | api_export.json | 2386 | False | | +| research_orders | 是 | pass | api_export.json | 378 | False | | +| research_risk | 是 | pass | api_export.json | 31 | False | | +| detail_results | 是 | pass | detail_api_export.json | 1485 | False | | +| detail_transactions | 是 | pass | detail_api_export.json | 378 | False | | +| detail_positions | 是 | pass | detail_api_export.json | 3675 | False | | +| detail_risk_tabs | 是 | pass | detail_api_export.json | 640 | | | +| platform_logs | 否 | warn | detail_api_export.json | 1000 | True | platform logs may be partial; audit_log is canonical | +| audit_log | 是 | pass | tabs_raw/audit_log.jsonl | 1630 | False | | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/summary_metrics.json b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/summary_metrics.json new file mode 100644 index 00000000..5d6c9898 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/summary_metrics.json @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/summary_metrics.json.gz", + + "original_path": "summary_metrics.json", + + "original_sha256": "317b30d9a2b2f3f72bb2dc7210bde94ef9e389e7939d8423c2b1f5ee5df3e21b", + + "compressed_sha256": "135fb1ab5e2fa8cf0bcebadf03780f0ed8d55c488b02372a3b7ccbd393629cc0", + + "original_bytes": 520 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/audit_log.jsonl b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/audit_log.jsonl new file mode 100644 index 00000000..7ec0f5a4 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/audit_log.jsonl @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/audit_log.jsonl.gz", + + "original_path": "tabs_raw/audit_log.jsonl", + + "original_sha256": "590310655c88b251a25813ac6ddcd0dd7d318bb88f3a5665a4cf61ca40757e51", + + "compressed_sha256": "8f264094a0c5c4b0c9fa2028fad6c1b77c97cb2cbad15e0880f52b32e83d94ac", + + "original_bytes": 1698903 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/balances.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/balances.md new file mode 100644 index 00000000..b067f58f --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/balances.md @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/balances.md.gz", + + "original_path": "tabs_raw/balances.md", + + "original_sha256": "7f0d0c1bee6b6572516b623634b2a9d2169fea08f48e8a6aa033984cc1b09872", + + "compressed_sha256": "a56c0800aceb2f51a891f68cd72f8c15229adebf668d89e8d51ef19b9f3d7ad6", + + "original_bytes": 90521 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/daily_returns.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/daily_returns.md new file mode 100644 index 00000000..c39df5ea --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/daily_returns.md @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/daily_returns.md.gz", + + "original_path": "tabs_raw/daily_returns.md", + + "original_sha256": "fae7f563d4decde5e0f5e2708b6502e4e712b42cb2ce35e8817779cc40060a50", + + "compressed_sha256": "8e31e63bf90b2d042d4f045a290db99eb6f3a0892a34462ee3caea975395f468", + + "original_bytes": 92691 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/logs.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/logs.md new file mode 100644 index 00000000..7e5e2a86 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/logs.md @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/logs.md.gz", + + "original_path": "tabs_raw/logs.md", + + "original_sha256": "d811c3e0aa782563080368e27c8bd9f2aa226afd30972ddc3c7cba7ca0fa2af3", + + "compressed_sha256": "f01427d06d60706892b923f8c27fba870fa5891fe71b9523b5c79748da6ee971", + + "original_bytes": 290 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/period_risks.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/period_risks.md new file mode 100644 index 00000000..196683fc --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/period_risks.md @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/period_risks.md.gz", + + "original_path": "tabs_raw/period_risks.md", + + "original_sha256": "661b2d392c210b2a0a17c1b5c5bfdf78e23dd67d577f87e008166997053e106c", + + "compressed_sha256": "ef3869e5e1cab71c32cb054dbd7d8decb3db98a6eb1c186d32983f8655ec4be4", + + "original_bytes": 62872 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/positioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/positioninfo.md new file mode 100644 index 00000000..5ee0b985 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/positioninfo.md @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/positioninfo.md.gz", + + "original_path": "tabs_raw/positioninfo.md", + + "original_sha256": "b66559bf2f878363ba35fa31fa8fcb6e0ad2f6c7249c723448db04164ba99e6b", + + "compressed_sha256": "2e8ffe3c2f7a5df3669a203e25084a9e1974f5a5c0a4e6ba18fc817b35ee87da", + + "original_bytes": 378767 + +} diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/profile.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/profile.md new file mode 100644 index 00000000..9a5a43e7 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/profile.md @@ -0,0 +1,601 @@ +# 性能分析 + +## fund_code + +- 总耗时:0.011969s + +```text +Total time: 0.011969 s +File: /tmp/strategy/user_code.py +Function: fund_code at line 129 +``` + +## format_etf_name + +- 总耗时:0.085193s + +```text +Total time: 0.085193 s +File: /tmp/strategy/user_code.py +Function: format_etf_name at line 131 +``` + +## build_etf_display_names + +- 总耗时:0.158997s + +```text +Total time: 0.158997 s +File: /tmp/strategy/user_code.py +Function: build_etf_display_names at line 144 +``` + +## fetch_etf_official_name + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: fetch_etf_official_name at line 151 +``` + +## load_etf_display_names + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: load_etf_display_names at line 161 +``` + +## _audit_jsonable + +- 总耗时:0.378645s + +```text +Total time: 0.378645 s +File: /tmp/strategy/user_code.py +Function: _audit_jsonable at line 169 +``` + +## _context_time_fields + +- 总耗时:0.308122s + +```text +Total time: 0.308122 s +File: /tmp/strategy/user_code.py +Function: _context_time_fields at line 198 +``` + +## audit_event + +- 总耗时:31.6594s + +```text +Total time: 31.6594 s +File: /tmp/strategy/user_code.py +Function: audit_event at line 205 +``` + +## _copy_runtime_default + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: _copy_runtime_default at line 219 +``` + +## _runtime_param + +- 总耗时:0.041921s + +```text +Total time: 0.041921 s +File: /tmp/strategy/user_code.py +Function: _runtime_param at line 227 +``` + +## _runtime_list_param + +- 总耗时:0.017167s + +```text +Total time: 0.017167 s +File: /tmp/strategy/user_code.py +Function: _runtime_list_param at line 231 +``` + +## snapshot_params + +- 总耗时:0.231506s + +```text +Total time: 0.231506 s +File: /tmp/strategy/user_code.py +Function: snapshot_params at line 236 +``` + +## validate_params + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: validate_params at line 326 +``` + +## resolve_ma_long_windows + +- 总耗时:0.001621s + +```text +Total time: 0.001621 s +File: /tmp/strategy/user_code.py +Function: resolve_ma_long_windows at line 414 +``` + +## resolve_crowd_thresholds + +- 总耗时:0.003103s + +```text +Total time: 0.003103 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_thresholds at line 419 +``` + +## resolve_crowd_ret_windows + +- 总耗时:0.001539s + +```text +Total time: 0.001539 s +File: /tmp/strategy/user_code.py +Function: resolve_crowd_ret_windows at line 428 +``` + +## initialize + +- 总耗时:0.148887s + +```text +Total time: 0.148887 s +File: /tmp/strategy/user_code.py +Function: initialize at line 435 +``` + +## set_parameter + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: set_parameter at line 505 +``` + +## _log_step + +- 总耗时:0.705124s + +```text +Total time: 0.705124 s +File: /tmp/strategy/user_code.py +Function: _log_step at line 576 +``` + +## compose_raw_weights + +- 总耗时:0.00395s + +```text +Total time: 0.00395 s +File: /tmp/strategy/user_code.py +Function: compose_raw_weights at line 582 +``` + +## _as_date + +- 总耗时:0.006395s + +```text +Total time: 0.006395 s +File: /tmp/strategy/user_code.py +Function: _as_date at line 592 +``` + +## _context_trade_date + +- 总耗时:0.089695s + +```text +Total time: 0.089695 s +File: /tmp/strategy/user_code.py +Function: _context_trade_date at line 603 +``` + +## _same_iso_week + +- 总耗时:0.00428s + +```text +Total time: 0.00428 s +File: /tmp/strategy/user_code.py +Function: _same_iso_week at line 605 +``` + +## _float_list + +- 总耗时:0.002799s + +```text +Total time: 0.002799 s +File: /tmp/strategy/user_code.py +Function: _float_list at line 611 +``` + +## _weekend_close_batch_id + +- 总耗时:0.002712s + +```text +Total time: 0.002712 s +File: /tmp/strategy/user_code.py +Function: _weekend_close_batch_id at line 618 +``` + +## _enrich_weekend_close_plan + +- 总耗时:0.198788s + +```text +Total time: 0.198788 s +File: /tmp/strategy/user_code.py +Function: _enrich_weekend_close_plan at line 621 +``` + +## _report_signal_plan + +- 总耗时:82.7943s + +```text +Total time: 82.7943 s +File: /tmp/strategy/user_code.py +Function: _report_signal_plan at line 637 +``` + +## _suppress_execution_notice + +- 总耗时:0.002616s + +```text +Total time: 0.002616 s +File: /tmp/strategy/user_code.py +Function: _suppress_execution_notice at line 648 +``` + +## _resume_execution_notice + +- 总耗时:0.002481s + +```text +Total time: 0.002481 s +File: /tmp/strategy/user_code.py +Function: _resume_execution_notice at line 660 +``` + +## build_rebalance_plan + +- 总耗时:59.8797s + +```text +Total time: 59.8797 s +File: /tmp/strategy/user_code.py +Function: build_rebalance_plan at line 670 +``` + +## weekly_check + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: weekly_check at line 739 +``` + +## prepare_delay_only_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: prepare_delay_only_rebalance at line 742 +``` + +## execute_pending_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_pending_rebalance at line 753 +``` + +## mark_live_like_signal_day + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: mark_live_like_signal_day at line 781 +``` + +## execute_live_like_rebalance + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: execute_live_like_rebalance at line 790 +``` + +## prepare_weekend_close_rebalance + +- 总耗时:148.299s + +```text +Total time: 148.299 s +File: /tmp/strategy/user_code.py +Function: prepare_weekend_close_rebalance at line 828 +``` + +## execute_weekend_close_rebalance + +- 总耗时:23.9867s + +```text +Total time: 23.9867 s +File: /tmp/strategy/user_code.py +Function: execute_weekend_close_rebalance at line 857 +``` + +## normalize_field_frame + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: normalize_field_frame at line 908 +``` + +## compute_history_count + +- 总耗时:0.00371s + +```text +Total time: 0.00371 s +File: /tmp/strategy/user_code.py +Function: compute_history_count at line 917 +``` + +## fetch_field + +- 总耗时:29.1131s + +```text +Total time: 29.1131 s +File: /tmp/strategy/user_code.py +Function: fetch_field at line 927 +``` + +## get_history_data + +- 总耗时:30.2038s + +```text +Total time: 30.2038 s +File: /tmp/strategy/user_code.py +Function: get_history_data at line 947 +``` + +## compute_trend_gates + +- 总耗时:0.714151s + +```text +Total time: 0.714151 s +File: /tmp/strategy/user_code.py +Function: compute_trend_gates at line 960 +``` + +## compute_momentum_scores + +- 总耗时:1.79872s + +```text +Total time: 1.79872 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_scores at line 976 +``` + +## select_topk + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: select_topk at line 1002 +``` + +## compute_rp_weights + +- 总耗时:0.408629s + +```text +Total time: 0.408629 s +File: /tmp/strategy/user_code.py +Function: compute_rp_weights at line 1011 +``` + +## compute_rsrs_multipliers + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_multipliers at line 1040 +``` + +## compute_rsrs_adjusted_scores + +- 总耗时:9.99111s + +```text +Total time: 9.99111 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_adjusted_scores at line 1049 +``` + +## compute_momentum_tilt_multipliers + +- 总耗时:0.027213s + +```text +Total time: 0.027213 s +File: /tmp/strategy/user_code.py +Function: compute_momentum_tilt_multipliers at line 1092 +``` + +## compute_rsrs_tilt_multipliers + +- 总耗时:10.0563s + +```text +Total time: 10.0563 s +File: /tmp/strategy/user_code.py +Function: compute_rsrs_tilt_multipliers at line 1113 +``` + +## apply_relative_tilts + +- 总耗时:0.013963s + +```text +Total time: 0.013963 s +File: /tmp/strategy/user_code.py +Function: apply_relative_tilts at line 1130 +``` + +## compute_crowd_penalties + +- 总耗时:9.03816s + +```text +Total time: 9.03816 s +File: /tmp/strategy/user_code.py +Function: compute_crowd_penalties at line 1146 +``` + +## percentile_rank + +- 总耗时:2.19101s + +```text +Total time: 2.19101 s +File: /tmp/strategy/user_code.py +Function: percentile_rank at line 1214 +``` + +## compute_portfolio_vol_scale_detail + +- 总耗时:0.351456s + +```text +Total time: 0.351456 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale_detail at line 1219 +``` + +## compute_portfolio_vol_scale + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_scale at line 1250 +``` + +## compute_portfolio_vol_asset_scales + +- 总耗时:0.872704s + +```text +Total time: 0.872704 s +File: /tmp/strategy/user_code.py +Function: compute_portfolio_vol_asset_scales at line 1253 +``` + +## apply_fixed_gold_vol_relief + +- 总耗时:0s + +```text +Total time: 0 s +File: /tmp/strategy/user_code.py +Function: apply_fixed_gold_vol_relief at line 1270 +``` + +## apply_dynamic_marginal_vol_relief + +- 总耗时:0.491186s + +```text +Total time: 0.491186 s +File: /tmp/strategy/user_code.py +Function: apply_dynamic_marginal_vol_relief at line 1293 +``` + +## select_dynamic_marginal_relief_asset + +- 总耗时:0.47858s + +```text +Total time: 0.47858 s +File: /tmp/strategy/user_code.py +Function: select_dynamic_marginal_relief_asset at line 1314 +``` + +## apply_weight_constraints + +- 总耗时:0.008776s + +```text +Total time: 0.008776 s +File: /tmp/strategy/user_code.py +Function: apply_weight_constraints at line 1349 +``` + +## execute_rebalance + +- 总耗时:19.2976s + +```text +Total time: 19.2976 s +File: /tmp/strategy/user_code.py +Function: execute_rebalance at line 1364 +``` diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/records.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/records.md new file mode 100644 index 00000000..8043b956 --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/records.md @@ -0,0 +1,3 @@ +# Record 记录 + +(无数据) diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/risk.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/risk.md new file mode 100644 index 00000000..8b40eebd --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/risk.md @@ -0,0 +1,37 @@ +# 风险指标 + +- 记录数:31 + +| metric | value | +| --- | --- | +| __version | 101 | +| algorithm_return | 1.2757260135000044 | +| algorithm_volatility | 0.08355847327292946 | +| alpha | 0.14071401978170212 | +| annual_algo_return | 0.1729054101184353 | +| annual_bm_return | -0.01552798313301329 | +| avg_excess_return | 0.0007606280372008427 | +| avg_position_days | 795.3333333333334 | +| avg_trade_return | 0.0504205778963544 | +| benchmark_return | -0.07752074822165167 | +| benchmark_volatility | 0.17880949192018716 | +| beta | 0.1406247665174847 | +| day_win_ratio | 0.5407292474786657 | +| excess_return | 1.466967153042067 | +| excess_return_max_drawdown | 0.2138423055497961 | +| excess_return_max_drawdown_period | ['2024-09-13', '2024-10-08'] | +| excess_return_sharpe | 0.8745209669226747 | +| information | 1.0886011443852281 | +| lose_count | 51 | +| max_drawdown | 0.07577864706331894 | +| max_drawdown_period | ['2021-11-22', '2022-01-28'] | +| max_leverage | 0.0 | +| period_label | 2026-04 | +| profit_loss_ratio | 5.008322011146711 | +| sharpe | 1.5905677175829014 | +| sortino | 2.351794463723918 | +| trading_days | 1289 | +| treasury_return | 0.21282191780821919 | +| turnover_rate | 0.020487745186870018 | +| win_count | 140 | +| win_ratio | 0.7329842931937173 | diff --git a/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/transactioninfo.md b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/transactioninfo.md new file mode 100644 index 00000000..bfaa388d --- /dev/null +++ b/strategies/etf_factor_rotation/backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d/tabs_raw/transactioninfo.md @@ -0,0 +1,21 @@ +{ + + "kind": "data_center_pointer", + + "dataset_id": "etf_factor_rotation_backtest_runs", + + "snapshot_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_snapshot": "research_datasets/etf_factor_rotation_backtest_runs/20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + + "dataset_file": "raw/transactioninfo.md.gz", + + "original_path": "tabs_raw/transactioninfo.md", + + "original_sha256": "9231003f977b453cf45f5064d02865af7d04a533f92034c9407db117c9e27c8d", + + "compressed_sha256": "5eea156208babdd4921f3702c17e435d5b16c015a4e14341cfc546293f948b86", + + "original_bytes": 31888 + +} diff --git a/strategies/etf_factor_rotation/etf_factor_rotation.py b/strategies/etf_factor_rotation/etf_factor_rotation.py index 0546bae2..ec965a74 100644 --- a/strategies/etf_factor_rotation/etf_factor_rotation.py +++ b/strategies/etf_factor_rotation/etf_factor_rotation.py @@ -113,7 +113,7 @@ "MinWeight": 0.05, "RebalanceThreshold": 0.03, "MaxTotalWeight": 1.0, - "ExecutionTimingMode": "baseline", + "ExecutionTimingMode": "weekend-close-signal-next-open", "use_real_price": False, "fq_mode": None, "history_buffer": 100, @@ -123,6 +123,7 @@ "baseline", "logic-2-delay-only", "logic-3-live-like", + "weekend-close-signal-next-open", ) DEFAULT_EXECUTION_TIMING_MODE = PARAM_DEFAULTS["ExecutionTimingMode"] PORTFOLIO_VOL_RELIEF_MODES = ( @@ -560,7 +561,7 @@ def initialize(context): time='open', reference_security='000300.XSHG' ) - else: + elif g.ExecutionTimingMode == "logic-3-live-like": run_weekly( mark_live_like_signal_day, weekday=1, @@ -572,6 +573,18 @@ def initialize(context): time='open', reference_security='000300.XSHG' ) + else: + run_weekly( + prepare_weekend_close_rebalance, + weekday=-1, + time='15:30', + force=False, + ) + run_daily( + execute_weekend_close_rebalance, + time='open', + reference_security='000300.XSHG' + ) def on_strategy_end(context): @@ -686,8 +699,10 @@ def set_parameter(context): g.audit_token = JQ_AUTO_AUDIT_TOKEN g.audit_path = "%s/%s.jsonl" % (JQ_AUTO_AUDIT_DIR, g.audit_token) g.audit_seq = 0 - g.pending_rebalances = [] - g.pending_live_like_signal_days = [] + if not hasattr(g, "pending_rebalances"): + g.pending_rebalances = [] + if not hasattr(g, "pending_live_like_signal_days"): + g.pending_live_like_signal_days = [] # ============================================================ @@ -730,15 +745,105 @@ def compose_raw_weights(tilted_weights, trend_gates, crowd_penalties): # ============================================================ # weekly_check — 周频调仓主函数 # ============================================================ +def _as_date(value): + """把聚宽日期、pandas 日期或字符串统一成 date。""" + if value is None: + return None + if isinstance(value, datetime): + return value.date() + if isinstance(value, date): + return value + try: + return pd.Timestamp(value).date() + except Exception: + return value + + def _context_trade_date(context): """返回当前任务对应的自然日。""" - current_dt = getattr(context, "current_dt", None) - if current_dt is not None and hasattr(current_dt, "date"): - return current_dt.date() - return current_dt + return _as_date(getattr(context, "current_dt", None)) + + +def _same_iso_week(left, right): + left = _as_date(left) + right = _as_date(right) + if left is None or right is None: + return False + return left.isocalendar()[:2] == right.isocalendar()[:2] + + +def _float_list(values): + try: + if hasattr(values, "tolist"): + values = values.tolist() + except Exception: + pass + return [float(value) for value in list(values or [])] + +def _weekend_close_batch_id(signal_date): + token = str(getattr(g, "audit_token", "manual")).replace("/", "_").replace("\\", "_") + return "%s-weekend-close-%s" % (token, signal_date) -def build_rebalance_plan(context): + +def _enrich_weekend_close_plan(context, plan, signal_date): + weights = _float_list(plan.get("final_weights")) + portfolio = getattr(context, "portfolio", None) + total_value = getattr(portfolio, "total_value", None) + plan["final_weights"] = weights + plan["target_weights"] = weights + plan["target_values"] = [total_value * weight for weight in weights] if total_value is not None else [] + plan["portfolio_total_value"] = total_value + plan["signal_date"] = signal_date + plan["asof_date"] = signal_date + plan["prepared_date"] = signal_date + plan["trade_date"] = plan.get("trade_date") + plan["batch_id"] = plan.get("batch_id") or _weekend_close_batch_id(signal_date) + plan["plan_cached"] = True + plan["signal_notice_sent"] = False + return plan + + +def _report_signal_plan(plan): + if FeishuRelayTools is None: + return False + reporter = getattr(FeishuRelayTools, "report_signal_plan", None) + if not callable(reporter): + return False + try: + return bool(reporter(plan)) + except Exception as exc: + log.warning("feishu signal notice failed: %s", exc) + return False + + +def _suppress_execution_notice(batch_id): + if FeishuRelayTools is None: + return False + suppress = getattr(FeishuRelayTools, "suppress_execution_notice", None) + if not callable(suppress): + return False + try: + suppress(batch_id=batch_id, reason="signal_notice_already_sent") + return True + except Exception as exc: + log.warning("feishu execution notice suppress failed: %s", exc) + return False + + +def _resume_execution_notice(): + if FeishuRelayTools is None: + return + resume = getattr(FeishuRelayTools, "resume_execution_notice", None) + if not callable(resume): + return + try: + resume() + except Exception as exc: + log.warning("feishu execution notice resume failed: %s", exc) + + +def build_rebalance_plan(context, asof_date=None): """生成一次调仓所需的完整信号快照,但不直接下单。 流程:TrendGate → RPWeight → MomentumScore → MomentumTilt @@ -750,7 +855,11 @@ def build_rebalance_plan(context): etf_names = params["etf_names"] # 1. 拉取历史数据 - prices = get_history_data(context, pool, params) + if asof_date is not None: + asof_date = _as_date(asof_date) + else: + asof_date = getattr(context, "previous_date", None) + prices = get_history_data(context, pool, params, asof_date=asof_date) # 2. 计算趋势门槛 trend_gates = compute_trend_gates(prices, pool, params) @@ -823,7 +932,7 @@ def build_rebalance_plan(context): final_weights_before_constraints=final_weights_before_constraints, final_weights=final_weights, execution_timing_mode=params["ExecutionTimingMode"], - asof_date=getattr(context, "previous_date", None), + asof_date=asof_date, trade_date=_context_trade_date(context), ) @@ -831,7 +940,7 @@ def build_rebalance_plan(context): "pool": pool, "final_weights": final_weights, "params": params, - "asof_date": getattr(context, "previous_date", None), + "asof_date": asof_date, "prepared_date": _context_trade_date(context), } @@ -945,6 +1054,95 @@ def execute_live_like_rebalance(context): queue.pop(0) +def prepare_weekend_close_rebalance(context): + """收盘后生成候选信号 plan,次开盘跨周时执行。""" + signal_date = _context_trade_date(context) + params = snapshot_params() + if signal_date is None: + audit_event( + "weekend_close_signal_skip", + context, + execution_timing_mode=params["ExecutionTimingMode"], + signal_date=signal_date, + reason="signal_date_unavailable", + ) + return + + plan = build_rebalance_plan(context, asof_date=signal_date) + plan = _enrich_weekend_close_plan(context, plan, signal_date) + plan["signal_notice_sent"] = _report_signal_plan(plan) + g.pending_rebalances = [plan] + audit_event( + "weekend_close_plan_cached", + context, + execution_timing_mode=plan["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=signal_date, + batch_id=plan["batch_id"], + target_weights=plan["target_weights"], + target_values=plan["target_values"], + signal_notice_sent=plan["signal_notice_sent"], + plan_cached=True, + final_weights=plan["final_weights"], + ) + + +def execute_weekend_close_rebalance(context): + """次交易日开盘执行已缓存的收盘信号 plan,不重新计算信号。""" + queue = getattr(g, "pending_rebalances", []) + if not queue: + return + + pending = queue[0] + trade_date = _context_trade_date(context) + signal_date = pending.get("signal_date") + if trade_date is None or signal_date is None: + audit_event( + "weekend_close_execute_wait", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + trade_date=trade_date, + ) + return + if _same_iso_week(signal_date, trade_date): + audit_event( + "weekend_close_plan_discarded", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + trade_date=trade_date, + reason="not_last_trade_day_of_week", + ) + g.pending_rebalances.pop(0) + return + + pending["trade_date"] = trade_date + suppress_reason = "signal_notice_already_sent" if pending.get("signal_notice_sent") else "" + notice_suppressed = _suppress_execution_notice(pending.get("batch_id")) if suppress_reason else False + audit_event( + "weekend_close_execute", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + batch_id=pending.get("batch_id"), + trade_date=trade_date, + execution_order_called=True, + execution_notice_suppressed=notice_suppressed, + suppress_reason=suppress_reason if notice_suppressed else "", + final_weights=pending["final_weights"], + ) + try: + execute_rebalance(context, pending["pool"], pending["final_weights"], pending["params"]) + finally: + if notice_suppressed: + _resume_execution_notice() + g.pending_rebalances.pop(0) + + # ============================================================ # normalize_field_frame — 数据返回结构归一化 # ============================================================ @@ -1026,7 +1224,7 @@ def fetch_field(pool, field, count, params, end_date=None): return result.dropna(how='all') -def get_history_data(context, pool, params): +def get_history_data(context, pool, params, asof_date=None): """ 拉取足够长的历史 OHLC + 成交额数据,并预计算日收益率。 @@ -1035,12 +1233,13 @@ def get_history_data(context, pool, params): close_ret: DataFrame(index=日期, columns=ETF代码),close 的 pct_change() """ needed = compute_history_count(params) + history_end_date = asof_date if asof_date is not None else getattr(context, "previous_date", None) prices = {} - prices['close'] = fetch_field(pool, 'close', needed, params, end_date=context.previous_date) - prices['high'] = fetch_field(pool, 'high', needed, params, end_date=context.previous_date) - prices['low'] = fetch_field(pool, 'low', needed, params, end_date=context.previous_date) - prices['amount'] = fetch_field(pool, 'money', needed, params, end_date=context.previous_date) + prices['close'] = fetch_field(pool, 'close', needed, params, end_date=history_end_date) + prices['high'] = fetch_field(pool, 'high', needed, params, end_date=history_end_date) + prices['low'] = fetch_field(pool, 'low', needed, params, end_date=history_end_date) + prices['amount'] = fetch_field(pool, 'money', needed, params, end_date=history_end_date) # 预计算日收益率,下游风险平价和组合波动率复用同一份 prices['close_ret'] = prices['close'].pct_change() @@ -1048,7 +1247,7 @@ def get_history_data(context, pool, params): # 数据新鲜度日志:确保历史数据不晚于 context.previous_date close_df = prices['close'] last_dt = close_df.index[-1] if len(close_df) else None - log.info("history end_date=%s, context.previous_date=%s", last_dt, context.previous_date) + log.info("history end_date=%s, asof_date=%s", last_dt, history_end_date) return prices diff --git a/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/manifest.json b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/manifest.json new file mode 100644 index 00000000..0ffe6933 --- /dev/null +++ b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/manifest.json @@ -0,0 +1,68 @@ +{ + "batch_id": "20260701-live-aligned-execution-gate", + "strategy": "etf_factor_rotation", + "created": "2026-07-01T00:00:00+08:00", + "status": "pending", + "scenarios": { + "s01-baseline-original": { + "status": "completed", + "runs": [ + { + "run_id": "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0", + "label": "default", + "params_diff": { + "ExecutionTimingMode": "baseline" + }, + "status": "completed" + } + ] + }, + "s02-logic3-code-variant": { + "status": "completed", + "runs": [ + { + "run_id": "20260701-0025-bt0d4e21c752867aa36b89ee13d7540352", + "label": "default", + "params_diff": {}, + "status": "completed" + } + ] + }, + "s03-weekend-close-signal-next-open-variant": { + "status": "superseded", + "superseded_by": "s04-weekend-close-signal-next-open-variant-v2", + "superseded_reason": "initial no-trade run; s04 is the active reproducible weekend-close variant evidence", + "runs": [ + { + "run_id": "20260701-0028-btcb35093c739b769b0c7cebb1bbe141b5", + "label": "default", + "params_diff": {}, + "status": "superseded" + } + ] + }, + "s04-weekend-close-signal-next-open-variant-v2": { + "status": "completed", + "runs": [ + { + "run_id": "20260701-0246-bt526c312fb836d6e07826ce170809486a", + "label": "default", + "params_diff": {}, + "status": "completed" + } + ] + }, + "s05-official-current-baseline": { + "runs": [ + { + "run_id": "20260701-0332-btbfc5066102fc7eac2f3dfd3da0276a3d", + "label": "default", + "params_diff": {}, + "status": "completed" + } + ], + "status": "completed" + } + }, + "updated": "2026-07-01T03:37:13" +} diff --git a/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s01-baseline-original/scenario.json b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s01-baseline-original/scenario.json new file mode 100644 index 00000000..14aa6ab5 --- /dev/null +++ b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s01-baseline-original/scenario.json @@ -0,0 +1,17 @@ +{ + "strategy_file": "../../../../etf_factor_rotation.py", + "strategy": "etf_factor_rotation", + "scenario_id": "s01-baseline-original", + "start_date": "2021-01-01", + "end_date": "2026-04-30", + "capital": 100000, + "frequency": "1d", + "py_version": "Python3", + "batch_id": "20260701-live-aligned-execution-gate", + "estimated_minutes": 6, + "result_source": "auto", + "allow_partial": false, + "params_diff": { + "ExecutionTimingMode": "baseline" + } +} diff --git a/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s02-logic3-code-variant/scenario.json b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s02-logic3-code-variant/scenario.json new file mode 100644 index 00000000..c490ea94 --- /dev/null +++ b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s02-logic3-code-variant/scenario.json @@ -0,0 +1,15 @@ +{ + "strategy_file": "../../../../variants/code/logic3_code_variant.py", + "strategy": "etf_factor_rotation", + "strategy_name": "etf_factor_rotation", + "scenario_id": "s02-logic3-code-variant", + "start_date": "2021-01-01", + "end_date": "2026-04-30", + "capital": 100000, + "frequency": "1d", + "py_version": "Python3", + "batch_id": "20260701-live-aligned-execution-gate", + "estimated_minutes": 6, + "result_source": "auto", + "allow_partial": false +} diff --git a/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s03-weekend-close-signal-next-open-variant/scenario.json b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s03-weekend-close-signal-next-open-variant/scenario.json new file mode 100644 index 00000000..a4997b6b --- /dev/null +++ b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s03-weekend-close-signal-next-open-variant/scenario.json @@ -0,0 +1,18 @@ +{ + "strategy_file": "../../../../variants/code/weekend_close_signal_next_open_variant.py", + "strategy": "etf_factor_rotation", + "strategy_name": "etf_factor_rotation", + "scenario_id": "s03-weekend-close-signal-next-open-variant", + "start_date": "2021-01-01", + "end_date": "2026-04-30", + "capital": 100000, + "frequency": "1d", + "py_version": "Python3", + "batch_id": "20260701-live-aligned-execution-gate", + "estimated_minutes": 6, + "result_source": "auto", + "allow_partial": false, + "status": "superseded", + "superseded_by": "s04-weekend-close-signal-next-open-variant-v2", + "superseded_reason": "initial no-trade run; s04 is the active reproducible weekend-close variant evidence" +} diff --git a/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s04-weekend-close-signal-next-open-variant-v2/scenario.json b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s04-weekend-close-signal-next-open-variant-v2/scenario.json new file mode 100644 index 00000000..81676fe0 --- /dev/null +++ b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s04-weekend-close-signal-next-open-variant-v2/scenario.json @@ -0,0 +1,15 @@ +{ + "strategy_file": "../../../../variants/code/weekend_close_signal_next_open_variant.py", + "strategy": "etf_factor_rotation", + "strategy_name": "etf_factor_rotation", + "scenario_id": "s04-weekend-close-signal-next-open-variant-v2", + "start_date": "2021-01-01", + "end_date": "2026-04-30", + "capital": 100000, + "frequency": "1d", + "py_version": "Python3", + "batch_id": "20260701-live-aligned-execution-gate", + "estimated_minutes": 6, + "result_source": "auto", + "allow_partial": false +} diff --git a/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s05-official-current-baseline/scenario.json b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s05-official-current-baseline/scenario.json new file mode 100644 index 00000000..50c9696a --- /dev/null +++ b/strategies/etf_factor_rotation/test_batches/20260701-live-aligned-execution-gate/scenarios/s05-official-current-baseline/scenario.json @@ -0,0 +1,15 @@ +{ + "strategy_file": "../../../../etf_factor_rotation.py", + "strategy": "etf_factor_rotation", + "strategy_name": "etf_factor_rotation", + "scenario_id": "s05-official-current-baseline", + "start_date": "2021-01-01", + "end_date": "2026-04-30", + "capital": 100000, + "frequency": "1d", + "py_version": "Python3", + "batch_id": "20260701-live-aligned-execution-gate", + "estimated_minutes": 6, + "result_source": "auto", + "allow_partial": false +} diff --git a/strategies/etf_factor_rotation/tests/conftest.py b/strategies/etf_factor_rotation/tests/conftest.py index 0bd13577..78ff4a20 100644 --- a/strategies/etf_factor_rotation/tests/conftest.py +++ b/strategies/etf_factor_rotation/tests/conftest.py @@ -24,7 +24,7 @@ # 策略文件第 1 行调用 enable_profile(),必须提前注入 builtins.enable_profile = Mock() -sys.modules['FeishuRelayTools'] = MagicMock() +sys.modules['FeishuRelayTools'] = SimpleNamespace() # ============================================================ @@ -206,7 +206,7 @@ def mock_g(strategy): MinWeight=0.05, RebalanceThreshold=0.03, MaxTotalWeight=1.0, - ExecutionTimingMode='baseline', + ExecutionTimingMode='weekend-close-signal-next-open', live_days=1250, history_buffer=100, benchmark='000300.XSHG', @@ -255,3 +255,5 @@ def _auto_reset_mocks(strategy): strategy.set_slippage.reset_mock() strategy.run_weekly.reset_mock() strategy.run_daily.reset_mock() + strategy._mock_portfolio.total_value = 100000.0 + strategy._mock_portfolio.positions = {} diff --git a/strategies/etf_factor_rotation/tests/test_etf_factor_rotation.py b/strategies/etf_factor_rotation/tests/test_etf_factor_rotation.py index e46c6056..42fbd042 100644 --- a/strategies/etf_factor_rotation/tests/test_etf_factor_rotation.py +++ b/strategies/etf_factor_rotation/tests/test_etf_factor_rotation.py @@ -12,6 +12,7 @@ import inspect import json import math +from pathlib import Path from types import SimpleNamespace from unittest.mock import MagicMock, Mock, patch, call @@ -37,6 +38,28 @@ def test_strategy_loads_without_feishu_relay_tools(monkeypatch): assert module.FeishuRelayTools is None +def test_fixed_time_weekly_schedules_do_not_pass_reference_security(): + strategy_root = Path(__file__).resolve().parent.parent + strategy_files = [ + strategy_root / "etf_factor_rotation.py", + strategy_root / "variants" / "code" / "weekend_close_signal_next_open_variant.py", + ] + + for strategy_file in strategy_files: + tree = ast.parse(strategy_file.read_text(encoding="utf-8")) + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + if getattr(node.func, "id", "") != "run_weekly": + continue + kwargs = {keyword.arg: keyword.value for keyword in node.keywords if keyword.arg} + time_node = kwargs.get("time") + is_fixed_time = isinstance(time_node, ast.Constant) and time_node.value != "open" + assert not (is_fixed_time and "reference_security" in kwargs), ( + "%s has fixed-time run_weekly with reference_security" % strategy_file + ) + + # ============================================================ # 可复用的价格序列生成工具 # ============================================================ @@ -122,7 +145,8 @@ def test_all_core_functions_exist(self, strategy): 'initialize', 'set_parameter', 'weekly_check', 'build_rebalance_plan', 'prepare_delay_only_rebalance', 'execute_pending_rebalance', 'mark_live_like_signal_day', - 'execute_live_like_rebalance', + 'execute_live_like_rebalance', 'prepare_weekend_close_rebalance', + 'execute_weekend_close_rebalance', 'get_history_data', 'compute_trend_gates', 'compute_momentum_scores', 'select_topk', 'compute_rp_weights', 'compute_rsrs_multipliers', 'compute_rsrs_adjusted_scores', @@ -192,6 +216,17 @@ def test_portfolio_vol_relief_params_default_to_dyn_marginal(self, strategy): assert g.DynamicVolReliefMomentumWindow == 20 assert g.DynamicVolReliefCovWindow == 40 + def test_set_parameter_preserves_pending_rebalance_state(self, strategy): + pending = [{"signal_date": "2026-05-15", "pool": ["159819.XSHE"]}] + live_like = ["2026-05-18"] + strategy.g.pending_rebalances = pending + strategy.g.pending_live_like_signal_days = live_like + + strategy.set_parameter(strategy) + + assert strategy.g.pending_rebalances is pending + assert strategy.g.pending_live_like_signal_days is live_like + class TestEtfDisplayNames: """测试基金展示名始终保留编号,供报告和日志检索。""" @@ -266,15 +301,21 @@ def test_initialize_calls_set_option(self, strategy): strategy.set_option.assert_any_call('use_real_price', False) strategy.set_option.assert_any_call('avoid_future_data', True) - def test_initialize_registers_weekly_task(self, strategy): + def test_initialize_registers_default_weekend_close_task(self, strategy): context = SimpleNamespace() context.portfolio = strategy._mock_portfolio strategy.initialize(context) - strategy.run_weekly.assert_called_once() - args, kwargs = strategy.run_weekly.call_args - assert kwargs['weekday'] == 1 - assert kwargs['time'] == 'open' - strategy.run_daily.assert_not_called() + strategy.run_weekly.assert_called_once_with( + strategy.prepare_weekend_close_rebalance, + weekday=-1, + time='15:30', + force=False, + ) + strategy.run_daily.assert_called_once_with( + strategy.execute_weekend_close_rebalance, + time='open', + reference_security='000300.XSHG', + ) def test_initialize_registers_delay_only_tasks(self, strategy): original_set_parameter = strategy.set_parameter @@ -316,6 +357,30 @@ def set_live_like(context): _, daily_kwargs = strategy.run_daily.call_args assert daily_kwargs['time'] == 'open' + def test_initialize_registers_weekend_close_task(self, strategy): + original_set_parameter = strategy.set_parameter + + def set_weekend_close(context): + original_set_parameter(context) + strategy.g.ExecutionTimingMode = "weekend-close-signal-next-open" + + context = SimpleNamespace() + context.portfolio = strategy._mock_portfolio + with patch.object(strategy, 'set_parameter', side_effect=set_weekend_close): + strategy.initialize(context) + + strategy.run_weekly.assert_called_once_with( + strategy.prepare_weekend_close_rebalance, + weekday=-1, + time='15:30', + force=False, + ) + strategy.run_daily.assert_called_once_with( + strategy.execute_weekend_close_rebalance, + time='open', + reference_security='000300.XSHG', + ) + def test_initialize_calls_set_order_cost(self, strategy): """验证 initialize 调用了 set_order_cost 并传递正确的费率参数。""" context = SimpleNamespace() @@ -334,13 +399,15 @@ def test_initialize_calls_set_slippage(self, strategy): kwargs = strategy.set_slippage.call_args[1] assert kwargs['type'] == 'fund' - def test_initialize_registers_reference_security(self, strategy): - """验证 run_weekly 注册了 reference_security='000300.XSHG'。""" + def test_initialize_keeps_reference_security_on_open_execution_only(self, strategy): + """固定时间收盘信号不传 reference_security,开盘执行保留参照标的。""" context = SimpleNamespace() context.portfolio = strategy._mock_portfolio strategy.initialize(context) - kwargs = strategy.run_weekly.call_args[1] - assert kwargs['reference_security'] == '000300.XSHG' + weekly_kwargs = strategy.run_weekly.call_args[1] + daily_kwargs = strategy.run_daily.call_args[1] + assert 'reference_security' not in weekly_kwargs + assert daily_kwargs['reference_security'] == '000300.XSHG' def test_initialize_calls_set_parameter(self, strategy): """验证 initialize 后 g 对象包含了 set_parameter 写入的关键参数。""" @@ -2184,6 +2251,118 @@ def test_live_like_executes_after_holiday_gap(self, strategy, mock_g): pd.Timestamp("2021-10-11").date() ] + def test_weekend_close_prepares_last_trade_day_and_executes_cached_plan_next_open(self, strategy, mock_g): + mock_g.ExecutionTimingMode = "weekend-close-signal-next-open" + friday_date = pd.Timestamp("2026-05-15").date() + monday_date = pd.Timestamp("2026-05-18").date() + plan = { + "pool": list(mock_g.etf_pool), + "final_weights": np.array([0.4, 0.3, 0.0]), + "params": strategy.snapshot_params(), + "signal_date": friday_date, + "asof_date": friday_date, + "prepared_date": friday_date, + } + friday_close = SimpleNamespace( + current_dt=pd.Timestamp("2026-05-15 15:30:00").to_pydatetime(), + previous_date=pd.Timestamp("2026-05-14").date(), + portfolio=strategy._mock_portfolio, + ) + monday_open = SimpleNamespace( + current_dt=pd.Timestamp("2026-05-18 09:30:00").to_pydatetime(), + previous_date=friday_date, + portfolio=strategy._mock_portfolio, + ) + + with patch.object(strategy, 'build_rebalance_plan', return_value=plan) as build: + strategy.prepare_weekend_close_rebalance(friday_close) + + build.assert_called_once_with(friday_close, asof_date=friday_date) + assert strategy.g.pending_rebalances == [plan] + + with patch.object(strategy, 'build_rebalance_plan') as build_again: + with patch.object(strategy, 'execute_rebalance') as execute: + strategy.execute_weekend_close_rebalance(monday_open) + + build_again.assert_not_called() + execute.assert_called_once_with( + monday_open, + plan["pool"], + plan["final_weights"], + plan["params"], + ) + assert strategy.g.pending_rebalances == [] + + def test_weekend_close_reports_signal_plan_and_suppresses_execution_notice(self, strategy, mock_g): + mock_g.ExecutionTimingMode = "weekend-close-signal-next-open" + friday_date = pd.Timestamp("2026-05-15").date() + monday_date = pd.Timestamp("2026-05-18").date() + params = strategy.snapshot_params() + plan = { + "pool": list(mock_g.etf_pool), + "final_weights": np.array([0.4, 0.3, 0.0]), + "params": params, + "signal_date": friday_date, + "asof_date": friday_date, + "prepared_date": friday_date, + } + feishu = SimpleNamespace( + report_signal_plan=Mock(return_value=True), + suppress_execution_notice=Mock(), + resume_execution_notice=Mock(), + ) + friday_close = SimpleNamespace( + current_dt=pd.Timestamp("2026-05-15 15:30:00").to_pydatetime(), + previous_date=pd.Timestamp("2026-05-14").date(), + portfolio=strategy._mock_portfolio, + ) + monday_open = SimpleNamespace( + current_dt=pd.Timestamp("2026-05-18 09:30:00").to_pydatetime(), + previous_date=friday_date, + portfolio=strategy._mock_portfolio, + ) + + with patch.object(strategy, "FeishuRelayTools", feishu): + with patch.object(strategy, "build_rebalance_plan", return_value=plan): + strategy.prepare_weekend_close_rebalance(friday_close) + + cached = strategy.g.pending_rebalances[0] + feishu.report_signal_plan.assert_called_once_with(cached) + assert cached["signal_notice_sent"] is True + assert cached["plan_cached"] is True + assert cached["batch_id"] + assert cached["target_weights"] == [0.4, 0.3, 0.0] + assert cached["target_values"] == [40000.0, 30000.0, 0.0] + + with patch.object(strategy, "execute_rebalance") as execute: + strategy.execute_weekend_close_rebalance(monday_open) + + feishu.suppress_execution_notice.assert_called_once_with( + batch_id=cached["batch_id"], + reason="signal_notice_already_sent", + ) + feishu.resume_execution_notice.assert_called_once_with() + execute.assert_called_once_with( + monday_open, + cached["pool"], + cached["final_weights"], + cached["params"], + ) + events = [ + json.loads(call_args[0][1]) + for call_args in strategy.write_file.call_args_list + ] + cached_event = next(event for event in events if event["event"] == "weekend_close_plan_cached") + execute_event = next(event for event in events if event["event"] == "weekend_close_execute") + assert cached_event["batch_id"] == cached["batch_id"] + assert cached_event["signal_notice_sent"] is True + assert cached_event["plan_cached"] is True + assert execute_event["batch_id"] == cached["batch_id"] + assert execute_event["trade_date"] == monday_date.isoformat() + assert execute_event["execution_order_called"] is True + assert execute_event["execution_notice_suppressed"] is True + assert execute_event["suppress_reason"] == "signal_notice_already_sent" + # ============================================================ # 10. R3 修复验证 — get_history_data 显式 end_date=context.previous_date @@ -2774,8 +2953,8 @@ def test_snapshot_backfills_missing_execution_timing_mode(self, strategy, mock_g params = strategy.snapshot_params() - assert params["ExecutionTimingMode"] == "baseline" - assert mock_g.ExecutionTimingMode == "baseline" + assert params["ExecutionTimingMode"] == "weekend-close-signal-next-open" + assert mock_g.ExecutionTimingMode == "weekend-close-signal-next-open" def test_snapshot_backfills_all_missing_runtime_params(self, strategy, mock_g): """新增策略参数必须能兼容模拟盘保留的旧 g。""" diff --git a/strategies/etf_factor_rotation/variants/baseline_original.json b/strategies/etf_factor_rotation/variants/baseline_original.json new file mode 100644 index 00000000..cfffdd7e --- /dev/null +++ b/strategies/etf_factor_rotation/variants/baseline_original.json @@ -0,0 +1,42 @@ +{ + "schema_version": 1, + "variant_id": "baseline_original", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "原始正式基线代码:本次实验对照组,来源为本分支起点 origin/main 的正式策略脚本。", + "code_source": { + "type": "git", + "path": "strategies/etf_factor_rotation/etf_factor_rotation.py", + "commit": "a2119d8e8a841e35af915abff625ce93e905e24a" + }, + "params_diff": {}, + "research_refs": [], + "backtest_refs": [ + "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0" + ], + "report_refs": [ + "strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md" + ], + "created_at": "2026-06-30T20:21:17+00:00", + "updated_at": "2026-06-30T20:21:17+00:00", + "owner": "research-platform", + "created_by": "research-platform", + "updated_by": "research-platform", + "lifecycle": "active", + "payload": { + "code_source": { + "type": "git", + "path": "strategies/etf_factor_rotation/etf_factor_rotation.py", + "commit": "a2119d8e8a841e35af915abff625ce93e905e24a" + }, + "owner": "research-platform", + "research_refs": [], + "backtest_refs": [ + "20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0" + ], + "report_refs": [ + "strategies/etf_factor_rotation/backtest_runs/20260701-0012-bt8d074a3098c6308f791ea33d8806d6f0/report/backtest_report.md" + ] + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/variants/code/logic3_code_variant.py b/strategies/etf_factor_rotation/variants/code/logic3_code_variant.py new file mode 100644 index 00000000..2530689f --- /dev/null +++ b/strategies/etf_factor_rotation/variants/code/logic3_code_variant.py @@ -0,0 +1,2024 @@ +# 策略名:ETF多因子轮动 +enable_profile() +STRATEGY_NAME = "ETF多因子轮动" +try: + import FeishuRelayTools +except ImportError: + FeishuRelayTools = None + +""" +============================================================ +策略名称:ETF 多因子轮动策略(相对倾斜版) +策略类型:周线级别、场内基金、多因子动态配置 +适用标的:159819.XSHE、513100.XSHG、518880.XSHG(中文名运行时通过聚宽 API 读取) + +核心思想: + 趋势门槛判断"能不能买",风险平价分配基础仓位, + 动量和 RSRS 作为资产间相对倾斜信号调整权重分配(不改变组合总仓位), + 拥挤度惩罚和组合波动率控制在过热或高波时平滑降仓。 + 剩余仓位保留现金,不重新归一化到满仓。 + +模块分工: + - 趋势门槛(硬过滤):按 ETF 专属趋势均线以上才可入选,0/1 离散 + - 风险平价(逆波动率):所有趋势成立资产参与,σ 越小权重越大 + - 动量倾斜(相对信号):多周期排名分数去均值,clip 到 [TiltMin, TiltMax]; + 可选将极端高分资产的动量倾斜压回中性 + - RSRS 倾斜(相对信号):High~Low 回归 β 标准化 × R² 去均值,clip 到 [TiltMin, TiltMax] + - 倾斜合成:RPWeight × MomentumTilt × RSRSTilt,活跃资产内重新归一化 + - 拥挤度惩罚(只减不加):五指标分位数均值,超阈值线性打折 + - 组合波动率控制(只缩不放):RawWeight 组合波动率超目标时等比缩放 + +核心公式: + TiltedWeight_i = normalize(RPWeight_i × MomentumTilt_i × RSRSTilt_i) + RawWeight_i = TiltedWeight_i × TrendGate_i × CrowdPenalty_i + FinalWeight_i = RawWeight_i × PortfolioVolAssetScale_i + +调仓频率:每周开盘检查一次 +============================================================ +""" + +import json +from datetime import date, datetime + +import numpy as np +import pandas as pd + + +# ============================================================ +# 常量 — 内部字段名到聚宽字段名的映射 +# ============================================================ +FIELD_MAP = { + "close": "close", + "high": "high", + "low": "low", + "amount": "money", +} + +JQ_AUTO_AUDIT_TOKEN = "manual" +JQ_AUTO_AUDIT_DIR = "jq_auto_audit" +PARAM_DEFAULTS = { + "etf_pool": ( + "159819.XSHE", + "513100.XSHG", + "518880.XSHG", + ), + "MA_long": 120, + "MA_long_by_etf": (20, 40, 100), + "MomShort": 20, + "MomMid": 60, + "MomLong": 120, + "w20": 0.2, + "w60": 0.3, + "w120": 0.5, + "TopK": 3, + "VolWindow": 60, + "annual_factor": 252, + "RSRS_N": 18, + "RSRS_M": 600, + "RSRS_NegativeFullCut": 1.8, + "RSRSMinMultiplier": 0.0, + "RSRSMaxMultiplier": 1.3, + "MomentumTiltStrength": 0.50, + "MomentumTiltMin": 0.70, + "MomentumTiltMax": 1.30, + "MomentumExtremeScoreStart": None, + "MomentumExtremeTiltCap": 1.00, + "RSRSTiltMin": 0.70, + "RSRSTiltMax": 1.30, + "CrowdWindow": 500, + "CrowdRetShort": 20, + "CrowdRetMid": 60, + "AmountMAWindow": 20, + "DeviationMAWindow": 20, + "CrowdVolWindow": 20, + "CrowdStart": 0.60, + "CrowdEnd": 0.95, + "MinCrowdPenalty": 0.30, + "CrowdStart_by_etf": (0.60, 0.60, 0.80), + "CrowdEnd_by_etf": None, + "MinCrowdPenalty_by_etf": None, + "CrowdRetShort_by_etf": None, + "CrowdRetMid_by_etf": None, + "PortfolioVolWindow": 40, + "TargetVol": 0.08, + "MaxPortfolioVolScale": 1.0, + "PortfolioVolReliefMode": "dyn_marginal", + "GoldVolReliefFraction": 0.5, + "GoldVolReliefMaxRatio": 2.0, + "DynamicVolReliefFraction": 1.0, + "DynamicVolReliefMaxRatio": 1.5, + "DynamicVolReliefMomentumWindow": 20, + "DynamicVolReliefCovWindow": 40, + "MaxWeight": 0.60, + "MinWeight": 0.05, + "RebalanceThreshold": 0.03, + "MaxTotalWeight": 1.0, + "ExecutionTimingMode": "logic-3-live-like", + "use_real_price": False, + "fq_mode": None, + "history_buffer": 100, + "benchmark": "000300.XSHG", +} +EXECUTION_TIMING_MODES = ( + "baseline", + "logic-2-delay-only", + "logic-3-live-like", + "weekend-close-signal-next-open", +) +DEFAULT_EXECUTION_TIMING_MODE = PARAM_DEFAULTS["ExecutionTimingMode"] +PORTFOLIO_VOL_RELIEF_MODES = ( + "baseline", + "fixed_gold", + "dyn_marginal", +) +PORTFOLIO_VOL_RELIEF_DEFAULTS = { + "PortfolioVolReliefMode": PARAM_DEFAULTS["PortfolioVolReliefMode"], + "GoldVolReliefFraction": PARAM_DEFAULTS["GoldVolReliefFraction"], + "GoldVolReliefMaxRatio": PARAM_DEFAULTS["GoldVolReliefMaxRatio"], + "DynamicVolReliefFraction": PARAM_DEFAULTS["DynamicVolReliefFraction"], + "DynamicVolReliefMaxRatio": PARAM_DEFAULTS["DynamicVolReliefMaxRatio"], + "DynamicVolReliefMomentumWindow": PARAM_DEFAULTS["DynamicVolReliefMomentumWindow"], + "DynamicVolReliefCovWindow": PARAM_DEFAULTS["DynamicVolReliefCovWindow"], +} + + +def fund_code(security): + """从聚宽证券代码中提取 6 位基金代码,用于报告和日志展示。""" + return str(security).split(".")[0] + + +def format_etf_name(security, name): + """保证基金显示名使用 中文名(聚宽代码) 标准格式。""" + security = str(security) + code = fund_code(security) + display_name = str(name).strip() if name is not None else str(security) + full_suffix = "(%s)" % security + short_suffix = "(%s)" % code + if display_name == security: + return display_name + if display_name.endswith(full_suffix): + return display_name + if code and display_name.endswith(short_suffix): + display_name = display_name[:-len(short_suffix)].strip() + return "%s%s" % (display_name, full_suffix) + + +def build_etf_display_names(pool, names=None): + """按 etf_pool 顺序生成带编号的基金显示名列表。""" + names = names or [] + result = [] + for i, etf in enumerate(pool): + base_name = names[i] if i < len(names) else etf + result.append(format_etf_name(etf, base_name)) + return result + + +def fetch_etf_official_name(security, fallback_name=None): + """通过聚宽 API 读取基金官方中文名,失败时回退到已有名称或代码。""" + try: + info = get_security_info(security) + for attr in ("display_name", "name"): + value = getattr(info, attr, None) + if value: + return str(value).strip() + except Exception as exc: + log.warning("fetch ETF official name failed: security=%s error=%s", security, exc) + + return fallback_name or str(security) + + +def load_etf_display_names(pool, fallback_names=None): + """从聚宽官方证券信息生成标准 ETF 显示名。""" + fallback_names = fallback_names or [] + names = [] + for i, etf in enumerate(pool): + fallback_name = fallback_names[i] if i < len(fallback_names) else None + official_name = fetch_etf_official_name(etf, fallback_name=fallback_name) + names.append(format_etf_name(etf, official_name)) + return names + + +def _audit_jsonable(value): + """Convert strategy/runtime values to JSON-safe audit payloads.""" + if value is None or isinstance(value, (str, bool, int, float)): + return value + if isinstance(value, (date, datetime)): + return value.isoformat() + try: + if isinstance(value, np.generic): + return _audit_jsonable(value.item()) + if isinstance(value, np.ndarray): + return [_audit_jsonable(item) for item in value.tolist()] + except Exception: + pass + try: + if isinstance(value, pd.Timestamp): + return value.isoformat() + if isinstance(value, (pd.Series, pd.Index)): + return [_audit_jsonable(item) for item in value.tolist()] + if isinstance(value, pd.DataFrame): + return [ + {str(k): _audit_jsonable(v) for k, v in row.items()} + for row in value.reset_index().to_dict(orient="records") + ] + except Exception: + pass + if isinstance(value, dict): + return {str(k): _audit_jsonable(v) for k, v in value.items()} + if isinstance(value, (list, tuple, set)): + return [_audit_jsonable(item) for item in value] + return str(value) + + +def _context_time_fields(context): + result = {} + for name in ("current_dt", "previous_date"): + value = getattr(context, name, None) if context is not None else None + if value is not None: + result[name] = _audit_jsonable(value) + return result + + +def audit_event(event, context=None, **payload): + """Write one complete business-audit event to JoinQuant research storage.""" + path = getattr(g, "audit_path", "") + if not path: + return + seq = getattr(g, "audit_seq", 0) + 1 + g.audit_seq = seq + row = { + "seq": seq, + "event": event, + "audit_token": getattr(g, "audit_token", ""), + } + row.update(_context_time_fields(context)) + row.update({key: _audit_jsonable(value) for key, value in payload.items()}) + write_file(path, json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n", append=True) + + +def _copy_runtime_default(value): + if isinstance(value, tuple): + return list(value) + if isinstance(value, list): + return list(value) + if isinstance(value, dict): + return dict(value) + return value + + +def _runtime_param(name, default): + if not hasattr(g, name): + setattr(g, name, _copy_runtime_default(default)) + return getattr(g, name) + + +def _runtime_list_param(name): + value = _runtime_param(name, PARAM_DEFAULTS[name]) + if value is None: + return None + return list(value) + + +# ============================================================ +# snapshot_params — 参数快照 +# ============================================================ +def snapshot_params(): + """ + 从 g 读取全部策略参数,返回只读快照 dict。 + + 核心计算函数通过接收 params 而非直接读 g,实现解耦。 + """ + etf_pool = _runtime_list_param("etf_pool") + raw_etf_names = _runtime_param("etf_names", etf_pool) + etf_names = build_etf_display_names(etf_pool, list(raw_etf_names)) + return { + "etf_pool": etf_pool, + "etf_names": etf_names, + "benchmark": _runtime_param("benchmark", PARAM_DEFAULTS["benchmark"]), + "MA_long": _runtime_param("MA_long", PARAM_DEFAULTS["MA_long"]), + "MA_long_by_etf": _runtime_list_param("MA_long_by_etf"), + "MomShort": _runtime_param("MomShort", PARAM_DEFAULTS["MomShort"]), + "MomMid": _runtime_param("MomMid", PARAM_DEFAULTS["MomMid"]), + "MomLong": _runtime_param("MomLong", PARAM_DEFAULTS["MomLong"]), + "w20": _runtime_param("w20", PARAM_DEFAULTS["w20"]), + "w60": _runtime_param("w60", PARAM_DEFAULTS["w60"]), + "w120": _runtime_param("w120", PARAM_DEFAULTS["w120"]), + "TopK": _runtime_param("TopK", PARAM_DEFAULTS["TopK"]), + "VolWindow": _runtime_param("VolWindow", PARAM_DEFAULTS["VolWindow"]), + "annual_factor": _runtime_param("annual_factor", PARAM_DEFAULTS["annual_factor"]), + "RSRS_N": _runtime_param("RSRS_N", PARAM_DEFAULTS["RSRS_N"]), + "RSRS_M": _runtime_param("RSRS_M", PARAM_DEFAULTS["RSRS_M"]), + "RSRS_NegativeFullCut": _runtime_param("RSRS_NegativeFullCut", PARAM_DEFAULTS["RSRS_NegativeFullCut"]), + "RSRSMinMultiplier": _runtime_param("RSRSMinMultiplier", PARAM_DEFAULTS["RSRSMinMultiplier"]), + "RSRSMaxMultiplier": _runtime_param("RSRSMaxMultiplier", PARAM_DEFAULTS["RSRSMaxMultiplier"]), + "MomentumTiltStrength": _runtime_param("MomentumTiltStrength", PARAM_DEFAULTS["MomentumTiltStrength"]), + "MomentumTiltMin": _runtime_param("MomentumTiltMin", PARAM_DEFAULTS["MomentumTiltMin"]), + "MomentumTiltMax": _runtime_param("MomentumTiltMax", PARAM_DEFAULTS["MomentumTiltMax"]), + "MomentumExtremeScoreStart": _runtime_param("MomentumExtremeScoreStart", PARAM_DEFAULTS["MomentumExtremeScoreStart"]), + "MomentumExtremeTiltCap": _runtime_param("MomentumExtremeTiltCap", PARAM_DEFAULTS["MomentumExtremeTiltCap"]), + "RSRSTiltMin": _runtime_param("RSRSTiltMin", PARAM_DEFAULTS["RSRSTiltMin"]), + "RSRSTiltMax": _runtime_param("RSRSTiltMax", PARAM_DEFAULTS["RSRSTiltMax"]), + "CrowdWindow": _runtime_param("CrowdWindow", PARAM_DEFAULTS["CrowdWindow"]), + "CrowdRetShort": _runtime_param("CrowdRetShort", PARAM_DEFAULTS["CrowdRetShort"]), + "CrowdRetMid": _runtime_param("CrowdRetMid", PARAM_DEFAULTS["CrowdRetMid"]), + "AmountMAWindow": _runtime_param("AmountMAWindow", PARAM_DEFAULTS["AmountMAWindow"]), + "DeviationMAWindow": _runtime_param("DeviationMAWindow", PARAM_DEFAULTS["DeviationMAWindow"]), + "CrowdVolWindow": _runtime_param("CrowdVolWindow", PARAM_DEFAULTS["CrowdVolWindow"]), + "CrowdStart": _runtime_param("CrowdStart", PARAM_DEFAULTS["CrowdStart"]), + "CrowdEnd": _runtime_param("CrowdEnd", PARAM_DEFAULTS["CrowdEnd"]), + "MinCrowdPenalty": _runtime_param("MinCrowdPenalty", PARAM_DEFAULTS["MinCrowdPenalty"]), + "CrowdStart_by_etf": _runtime_list_param("CrowdStart_by_etf"), + "CrowdEnd_by_etf": _runtime_list_param("CrowdEnd_by_etf"), + "MinCrowdPenalty_by_etf": _runtime_list_param("MinCrowdPenalty_by_etf"), + "CrowdRetShort_by_etf": _runtime_list_param("CrowdRetShort_by_etf"), + "CrowdRetMid_by_etf": _runtime_list_param("CrowdRetMid_by_etf"), + "PortfolioVolWindow": _runtime_param("PortfolioVolWindow", PARAM_DEFAULTS["PortfolioVolWindow"]), + "TargetVol": _runtime_param("TargetVol", PARAM_DEFAULTS["TargetVol"]), + "MaxPortfolioVolScale": _runtime_param("MaxPortfolioVolScale", PARAM_DEFAULTS["MaxPortfolioVolScale"]), + "PortfolioVolReliefMode": _runtime_param( + "PortfolioVolReliefMode", + PORTFOLIO_VOL_RELIEF_DEFAULTS["PortfolioVolReliefMode"], + ), + "GoldVolReliefFraction": _runtime_param( + "GoldVolReliefFraction", + PORTFOLIO_VOL_RELIEF_DEFAULTS["GoldVolReliefFraction"], + ), + "GoldVolReliefMaxRatio": _runtime_param( + "GoldVolReliefMaxRatio", + PORTFOLIO_VOL_RELIEF_DEFAULTS["GoldVolReliefMaxRatio"], + ), + "DynamicVolReliefFraction": _runtime_param( + "DynamicVolReliefFraction", + PORTFOLIO_VOL_RELIEF_DEFAULTS["DynamicVolReliefFraction"], + ), + "DynamicVolReliefMaxRatio": _runtime_param( + "DynamicVolReliefMaxRatio", + PORTFOLIO_VOL_RELIEF_DEFAULTS["DynamicVolReliefMaxRatio"], + ), + "DynamicVolReliefMomentumWindow": _runtime_param( + "DynamicVolReliefMomentumWindow", + PORTFOLIO_VOL_RELIEF_DEFAULTS["DynamicVolReliefMomentumWindow"], + ), + "DynamicVolReliefCovWindow": _runtime_param( + "DynamicVolReliefCovWindow", + PORTFOLIO_VOL_RELIEF_DEFAULTS["DynamicVolReliefCovWindow"], + ), + "MaxWeight": _runtime_param("MaxWeight", PARAM_DEFAULTS["MaxWeight"]), + "MinWeight": _runtime_param("MinWeight", PARAM_DEFAULTS["MinWeight"]), + "RebalanceThreshold": _runtime_param("RebalanceThreshold", PARAM_DEFAULTS["RebalanceThreshold"]), + "MaxTotalWeight": _runtime_param("MaxTotalWeight", PARAM_DEFAULTS["MaxTotalWeight"]), + "ExecutionTimingMode": _runtime_param( + "ExecutionTimingMode", + DEFAULT_EXECUTION_TIMING_MODE, + ), + "use_real_price": _runtime_param("use_real_price", PARAM_DEFAULTS["use_real_price"]), + "fq_mode": _runtime_param("fq_mode", PARAM_DEFAULTS["fq_mode"]), + "history_buffer": _runtime_param("history_buffer", PARAM_DEFAULTS["history_buffer"]), + "audit_token": getattr(g, "audit_token", ""), + "audit_path": getattr(g, "audit_path", ""), + } + + +# ============================================================ +# validate_params — 参数校验 +# ============================================================ +def validate_params(params): + """ + 校验参数合法性,不合法时抛出 ValueError。 + + 校验规则来自技术实现方案 4.1 节参数校验表。 + """ + errors = [] + + if params["MA_long"] <= 0: + errors.append("MA_long must be positive") + ma_long_by_etf = params.get("MA_long_by_etf") + if ma_long_by_etf is not None: + if not isinstance(ma_long_by_etf, (list, tuple)): + errors.append("MA_long_by_etf must be a list or tuple when provided") + else: + if len(ma_long_by_etf) != len(params["etf_pool"]): + errors.append("MA_long_by_etf length must match etf_pool") + for window in ma_long_by_etf: + if not isinstance(window, (int, float)) or window <= 0: + errors.append("MA_long_by_etf values must be positive") + break + if abs(params["w20"] + params["w60"] + params["w120"] - 1.0) > 1e-8: + errors.append("momentum weights must sum to 1") + if params["TopK"] < 1: + errors.append("TopK must be >= 1") + if not (0 < params["MaxWeight"] <= params["MaxTotalWeight"] <= 1): + errors.append("MaxWeight must be in (0, MaxTotalWeight] and MaxTotalWeight <= 1") + if not (0 <= params["MinWeight"] <= params["MaxWeight"]): + errors.append("MinWeight must be in [0, MaxWeight]") + if params["TargetVol"] <= 0: + errors.append("TargetVol must be positive") + if params["PortfolioVolReliefMode"] not in PORTFOLIO_VOL_RELIEF_MODES: + errors.append("PortfolioVolReliefMode must be one of %s" % (PORTFOLIO_VOL_RELIEF_MODES,)) + for fraction_name in ("GoldVolReliefFraction", "DynamicVolReliefFraction"): + value = params[fraction_name] + if not (0.0 <= value <= 1.0): + errors.append("%s must be in [0.0, 1.0]" % fraction_name) + for ratio_name in ("GoldVolReliefMaxRatio", "DynamicVolReliefMaxRatio"): + value = params[ratio_name] + if value <= 1.0: + errors.append("%s must be > 1.0" % ratio_name) + for window_name in ("DynamicVolReliefMomentumWindow", "DynamicVolReliefCovWindow"): + value = params[window_name] + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + errors.append("%s must be a positive integer" % window_name) + if params["RSRS_M"] <= 0 or params["RSRS_N"] <= 1: + errors.append("RSRS_M must be positive and RSRS_N must be > 1") + if not (0 <= params["CrowdStart"] < params["CrowdEnd"] <= 1): + errors.append("Crowd thresholds must satisfy 0 <= CrowdStart < CrowdEnd <= 1") + for per_etf_name in ("CrowdStart_by_etf", "CrowdEnd_by_etf", "MinCrowdPenalty_by_etf", + "CrowdRetShort_by_etf", "CrowdRetMid_by_etf"): + per_etf_val = params.get(per_etf_name) + if per_etf_val is not None: + if not isinstance(per_etf_val, (list, tuple)): + errors.append("%s must be a list or tuple when provided" % per_etf_name) + elif len(per_etf_val) != len(params["etf_pool"]): + errors.append("%s length must match etf_pool" % per_etf_name) + starts_etf = params.get("CrowdStart_by_etf") + ends_etf = params.get("CrowdEnd_by_etf") + if starts_etf is not None and ends_etf is not None: + for idx, (s, e) in enumerate(zip(starts_etf, ends_etf)): + if not (0 <= s < e <= 1): + errors.append("CrowdStart_by_etf[%d]=%s must satisfy 0 <= start < end <= 1, got end=%s" % (idx, s, e)) + break + elif starts_etf is not None: + for idx, s in enumerate(starts_etf): + if not (0 <= s < params["CrowdEnd"] <= 1): + errors.append("CrowdStart_by_etf[%d]=%s must satisfy 0 <= start < CrowdEnd=%s" % (idx, s, params["CrowdEnd"])) + break + elif ends_etf is not None: + for idx, e in enumerate(ends_etf): + if not (params["CrowdStart"] < e <= 1): + errors.append("CrowdEnd_by_etf[%d]=%s must satisfy CrowdStart=%s < end <= 1" % (idx, e, params["CrowdStart"])) + break + if params["MomentumTiltStrength"] < 0: + errors.append("MomentumTiltStrength must be >= 0") + if not (0 < params["MomentumTiltMin"] <= 1 <= params["MomentumTiltMax"]): + errors.append("Momentum tilt bounds must satisfy 0 < min <= 1 <= max") + extreme_start = params["MomentumExtremeScoreStart"] + if extreme_start is not None and not (0 < extreme_start <= 1): + errors.append("MomentumExtremeScoreStart must be None or satisfy 0 < start <= 1") + if not (1 <= params["MomentumExtremeTiltCap"] <= params["MomentumTiltMax"]): + errors.append("MomentumExtremeTiltCap must satisfy 1 <= cap <= MomentumTiltMax") + if not (0 < params["RSRSTiltMin"] <= 1 <= params["RSRSTiltMax"]): + errors.append("RSRS tilt bounds must satisfy 0 < min <= 1 <= max") + if params["ExecutionTimingMode"] not in EXECUTION_TIMING_MODES: + errors.append("ExecutionTimingMode must be one of %s" % (EXECUTION_TIMING_MODES,)) + if len(params["etf_pool"]) != len(params["etf_names"]): + errors.append("etf_pool and etf_names must have the same length") + for etf, name in zip(params["etf_pool"], params["etf_names"]): + if str(etf) not in str(name): + errors.append("etf_names must include JoinQuant security code: %s" % etf) + + if errors: + raise ValueError("; ".join(errors)) + + +def resolve_ma_long_windows(params): + """返回与 etf_pool 对齐的趋势均线窗口。""" + ma_long_by_etf = params.get("MA_long_by_etf") + if ma_long_by_etf is None: + return [params["MA_long"]] * len(params["etf_pool"]) + return list(ma_long_by_etf) + + +def resolve_crowd_thresholds(params): + """返回与 etf_pool 对齐的 (CrowdStart, CrowdEnd, MinCrowdPenalty) 三元组列表。""" + n = len(params["etf_pool"]) + starts = params.get("CrowdStart_by_etf") + starts = list(starts) if starts is not None else [params["CrowdStart"]] * n + ends = params.get("CrowdEnd_by_etf") + ends = list(ends) if ends is not None else [params["CrowdEnd"]] * n + mins = params.get("MinCrowdPenalty_by_etf") + mins = list(mins) if mins is not None else [params["MinCrowdPenalty"]] * n + return list(zip(starts, ends, mins)) + + +def resolve_crowd_ret_windows(params): + """返回与 etf_pool 对齐的 (CrowdRetShort, CrowdRetMid) 二元组列表。""" + n = len(params["etf_pool"]) + shorts = params.get("CrowdRetShort_by_etf") + shorts = list(shorts) if shorts is not None else [params["CrowdRetShort"]] * n + mids = params.get("CrowdRetMid_by_etf") + mids = list(mids) if mids is not None else [params["CrowdRetMid"]] * n + return list(zip(shorts, mids)) + + +# ============================================================ +# initialize — 策略初始化 +# ============================================================ +def initialize(context): + """ + 由聚宽框架在回测/模拟启动时自动调用一次。 + + 作用: + - 向 g 对象写入全部策略参数 + - 设置交易费用(场内基金免印花税,佣金万分之一) + - 设置固定滑点 0 + - 注册每周开盘调仓任务 + """ + set_parameter(context) + validate_params(snapshot_params()) + write_file(g.audit_path, "", append=False) + audit_event("run_start", context, params=snapshot_params()) + set_option('use_real_price', g.use_real_price) + set_option("avoid_future_data", True) + + set_order_cost( + OrderCost( + open_tax=0, + close_tax=0, + open_commission=0.0001, + close_commission=0.0001, + min_commission=0 + ), + type='fund' + ) + + set_slippage(FixedSlippage(0.0), type='fund') + + if g.ExecutionTimingMode == "baseline": + run_weekly( + weekly_check, + weekday=1, + time='open', + reference_security='000300.XSHG' + ) + elif g.ExecutionTimingMode == "logic-2-delay-only": + run_weekly( + prepare_delay_only_rebalance, + weekday=1, + time='open', + reference_security='000300.XSHG' + ) + run_daily( + execute_pending_rebalance, + time='open', + reference_security='000300.XSHG' + ) + elif g.ExecutionTimingMode == "logic-3-live-like": + run_weekly( + mark_live_like_signal_day, + weekday=1, + time='open', + reference_security='000300.XSHG' + ) + run_daily( + execute_live_like_rebalance, + time='open', + reference_security='000300.XSHG' + ) + else: + run_daily( + prepare_weekend_close_rebalance, + time='15:30', + reference_security='000300.XSHG' + ) + run_daily( + execute_weekend_close_rebalance, + time='open', + reference_security='000300.XSHG' + ) + + +def on_strategy_end(context): + """Record the final audit marker used by local completeness checks.""" + portfolio = getattr(context, "portfolio", None) + audit_event( + "run_end", + context, + total_value=getattr(portfolio, "total_value", None), + cash=getattr(portfolio, "cash", None), + ) + + +# ============================================================ +# set_parameter — 策略参数集中设置 +# ============================================================ +def set_parameter(context): + """ + 将所有策略参数写入 g 全局对象,便于集中管理和回测参数扫描。 + + 参数类别:资产池、趋势门槛、动量选择、风险平价、RSRS 修正、 + 拥挤度惩罚、组合波动率控制、仓位交易约束。 + """ + + # ---- 资产池 ---- + g.etf_pool = _copy_runtime_default(PARAM_DEFAULTS["etf_pool"]) + g.etf_names = load_etf_display_names(g.etf_pool) + + # ---- 趋势门槛 ---- + g.MA_long = PARAM_DEFAULTS["MA_long"] + g.MA_long_by_etf = _copy_runtime_default(PARAM_DEFAULTS["MA_long_by_etf"]) + + # ---- 动量选择 ---- + g.MomShort = PARAM_DEFAULTS["MomShort"] + g.MomMid = PARAM_DEFAULTS["MomMid"] + g.MomLong = PARAM_DEFAULTS["MomLong"] + g.w20 = PARAM_DEFAULTS["w20"] + g.w60 = PARAM_DEFAULTS["w60"] + g.w120 = PARAM_DEFAULTS["w120"] + g.TopK = PARAM_DEFAULTS["TopK"] + + # ---- 风险平价 ---- + g.VolWindow = PARAM_DEFAULTS["VolWindow"] + g.annual_factor = PARAM_DEFAULTS["annual_factor"] + + # ---- RSRS 修正 ---- + g.RSRS_N = PARAM_DEFAULTS["RSRS_N"] # 回归窗口 + g.RSRS_M = PARAM_DEFAULTS["RSRS_M"] # 标准化窗口 + g.RSRS_NegativeFullCut = PARAM_DEFAULTS["RSRS_NegativeFullCut"] + g.RSRSMinMultiplier = PARAM_DEFAULTS["RSRSMinMultiplier"] + g.RSRSMaxMultiplier = PARAM_DEFAULTS["RSRSMaxMultiplier"] + + # ---- 动量倾斜(资产间相对信号) ---- + g.MomentumTiltStrength = PARAM_DEFAULTS["MomentumTiltStrength"] + g.MomentumTiltMin = PARAM_DEFAULTS["MomentumTiltMin"] + g.MomentumTiltMax = PARAM_DEFAULTS["MomentumTiltMax"] + g.MomentumExtremeScoreStart = PARAM_DEFAULTS["MomentumExtremeScoreStart"] + g.MomentumExtremeTiltCap = PARAM_DEFAULTS["MomentumExtremeTiltCap"] + + # ---- RSRS 倾斜(资产间相对信号) ---- + g.RSRSTiltMin = PARAM_DEFAULTS["RSRSTiltMin"] + g.RSRSTiltMax = PARAM_DEFAULTS["RSRSTiltMax"] + + # ---- 拥挤度惩罚 ---- + g.CrowdWindow = PARAM_DEFAULTS["CrowdWindow"] + g.CrowdRetShort = PARAM_DEFAULTS["CrowdRetShort"] + g.CrowdRetMid = PARAM_DEFAULTS["CrowdRetMid"] + g.AmountMAWindow = PARAM_DEFAULTS["AmountMAWindow"] + g.DeviationMAWindow = PARAM_DEFAULTS["DeviationMAWindow"] + g.CrowdVolWindow = PARAM_DEFAULTS["CrowdVolWindow"] + g.CrowdStart = PARAM_DEFAULTS["CrowdStart"] + g.CrowdEnd = PARAM_DEFAULTS["CrowdEnd"] + g.MinCrowdPenalty = PARAM_DEFAULTS["MinCrowdPenalty"] + g.CrowdStart_by_etf = _copy_runtime_default(PARAM_DEFAULTS["CrowdStart_by_etf"]) + g.CrowdEnd_by_etf = PARAM_DEFAULTS["CrowdEnd_by_etf"] + g.MinCrowdPenalty_by_etf = PARAM_DEFAULTS["MinCrowdPenalty_by_etf"] + g.CrowdRetShort_by_etf = PARAM_DEFAULTS["CrowdRetShort_by_etf"] + g.CrowdRetMid_by_etf = PARAM_DEFAULTS["CrowdRetMid_by_etf"] + + # ---- 组合波动率控制 ---- + g.PortfolioVolWindow = PARAM_DEFAULTS["PortfolioVolWindow"] + g.TargetVol = PARAM_DEFAULTS["TargetVol"] + g.MaxPortfolioVolScale = PARAM_DEFAULTS["MaxPortfolioVolScale"] + g.PortfolioVolReliefMode = PARAM_DEFAULTS["PortfolioVolReliefMode"] + g.GoldVolReliefFraction = PARAM_DEFAULTS["GoldVolReliefFraction"] + g.GoldVolReliefMaxRatio = PARAM_DEFAULTS["GoldVolReliefMaxRatio"] + g.DynamicVolReliefFraction = PARAM_DEFAULTS["DynamicVolReliefFraction"] + g.DynamicVolReliefMaxRatio = PARAM_DEFAULTS["DynamicVolReliefMaxRatio"] + g.DynamicVolReliefMomentumWindow = PARAM_DEFAULTS["DynamicVolReliefMomentumWindow"] + g.DynamicVolReliefCovWindow = PARAM_DEFAULTS["DynamicVolReliefCovWindow"] + + # ---- 仓位与交易约束 ---- + g.MaxWeight = PARAM_DEFAULTS["MaxWeight"] + g.MinWeight = PARAM_DEFAULTS["MinWeight"] + g.RebalanceThreshold = PARAM_DEFAULTS["RebalanceThreshold"] + g.MaxTotalWeight = PARAM_DEFAULTS["MaxTotalWeight"] + g.ExecutionTimingMode = DEFAULT_EXECUTION_TIMING_MODE + + # ---- 数据与基准 ---- + # 复权模式:fq='pre' 在 FQ A/B 对比测试中证实对场内基金会 + # 导致 get_price 返回空数据(2025-04~2026-04 区间复现)。 + # 故默认关闭复权。参考 FQ comparison: R2/backtest_runs/*/report/fq-comparison.md + g.use_real_price = PARAM_DEFAULTS["use_real_price"] + g.fq_mode = PARAM_DEFAULTS["fq_mode"] # 不复权(场内基金默认) + ma_long_max = max(g.MA_long_by_etf) if g.MA_long_by_etf else g.MA_long + g.live_days = max( + ma_long_max, g.MomLong, g.RSRS_M, + g.CrowdWindow, g.PortfolioVolWindow + ) + 50 + g.history_buffer = PARAM_DEFAULTS["history_buffer"] + g.benchmark = PARAM_DEFAULTS["benchmark"] + g.audit_token = JQ_AUTO_AUDIT_TOKEN + g.audit_path = "%s/%s.jsonl" % (JQ_AUTO_AUDIT_DIR, g.audit_token) + g.audit_seq = 0 + g.pending_rebalances = [] + g.pending_live_like_signal_days = [] + + +# ============================================================ +# _log_step — 调仓中间量诊断日志 +# ============================================================ +def _log_step(name, cn_name, pool, values, fmt=".4f", etf_names=None): + """ + 以 "[中文名] name: 基金名(聚宽代码)=value" 格式逐只打印调仓中间量,便于云端回测诊断。 + + 不在本地单测中验证日志格式,只保证聚宽云端 log.info 可输出。 + """ + labels = build_etf_display_names(pool, etf_names) + parts = ["%s=%%s" % label for label in labels] + template = "[%s] %s: " % (cn_name, name) + ", ".join(parts) + formatted = tuple(format(v, fmt) for v in values) + log.info(template, *formatted) + + +# ============================================================ +# compose_raw_weights — 权重合成 +# ============================================================ +def compose_raw_weights(tilted_weights, trend_gates, crowd_penalties): + """ + 合成各模块输出为 RawWeight。 + + 动量与 RSRS 已经体现在 TiltedWeight 中,不再作为独立乘数。 + TrendGate 仍保留作为二次保护。不重新归一化。 + """ + n = len(tilted_weights) + raw = np.zeros(n) + for i in range(n): + raw[i] = ( + tilted_weights[i] + * trend_gates[i] + * crowd_penalties[i] + ) + return raw + + +# ============================================================ +# weekly_check — 周频调仓主函数 +# ============================================================ +def _as_date(value): + """把聚宽日期、pandas 日期或字符串统一成 date。""" + if value is None: + return None + if isinstance(value, datetime): + return value.date() + if isinstance(value, date): + return value + try: + return pd.Timestamp(value).date() + except Exception: + return value + + +def _context_trade_date(context): + """返回当前任务对应的自然日。""" + return _as_date(getattr(context, "current_dt", None)) + + +def _same_iso_week(left, right): + left = _as_date(left) + right = _as_date(right) + if left is None or right is None: + return False + return left.isocalendar()[:2] == right.isocalendar()[:2] + + +def _float_list(values): + try: + if hasattr(values, "tolist"): + values = values.tolist() + except Exception: + pass + return [float(value) for value in list(values or [])] + + +def _weekend_close_batch_id(signal_date): + token = str(getattr(g, "audit_token", "manual")).replace("/", "_").replace("\\", "_") + return "%s-weekend-close-%s" % (token, signal_date) + + +def _enrich_weekend_close_plan(context, plan, signal_date): + weights = _float_list(plan.get("final_weights")) + portfolio = getattr(context, "portfolio", None) + total_value = getattr(portfolio, "total_value", None) + plan["final_weights"] = weights + plan["target_weights"] = weights + plan["target_values"] = [total_value * weight for weight in weights] if total_value is not None else [] + plan["portfolio_total_value"] = total_value + plan["signal_date"] = signal_date + plan["asof_date"] = signal_date + plan["prepared_date"] = signal_date + plan["trade_date"] = plan.get("trade_date") + plan["batch_id"] = plan.get("batch_id") or _weekend_close_batch_id(signal_date) + plan["plan_cached"] = True + plan["signal_notice_sent"] = False + return plan + + +def _report_signal_plan(plan): + if FeishuRelayTools is None: + return False + reporter = getattr(FeishuRelayTools, "report_signal_plan", None) + if not callable(reporter): + return False + try: + return bool(reporter(plan)) + except Exception as exc: + log.warning("feishu signal notice failed: %s", exc) + return False + + +def _suppress_execution_notice(batch_id): + if FeishuRelayTools is None: + return False + suppress = getattr(FeishuRelayTools, "suppress_execution_notice", None) + if not callable(suppress): + return False + try: + suppress(batch_id=batch_id, reason="signal_notice_already_sent") + return True + except Exception as exc: + log.warning("feishu execution notice suppress failed: %s", exc) + return False + + +def _resume_execution_notice(): + if FeishuRelayTools is None: + return + resume = getattr(FeishuRelayTools, "resume_execution_notice", None) + if not callable(resume): + return + try: + resume() + except Exception as exc: + log.warning("feishu execution notice resume failed: %s", exc) + + +def build_rebalance_plan(context, asof_date=None): + """生成一次调仓所需的完整信号快照,但不直接下单。 + + 流程:TrendGate → RPWeight → MomentumScore → MomentumTilt + → RSRSTilt → TiltedWeight → CrowdPenalty + → PortfolioVolScale → FinalWeight + """ + params = snapshot_params() + pool = params["etf_pool"] + etf_names = params["etf_names"] + + # 1. 拉取历史数据 + if asof_date is not None: + asof_date = _as_date(asof_date) + else: + asof_date = getattr(context, "previous_date", None) + prices = get_history_data(context, pool, params, asof_date=asof_date) + + # 2. 计算趋势门槛 + trend_gates = compute_trend_gates(prices, pool, params) + _log_step("TrendGate", "趋势门槛", pool, trend_gates, fmt=".0f", etf_names=etf_names) + + # 3. 风险平价基础权重(所有趋势成立资产参与) + active_mask = [gate > 0 for gate in trend_gates] + rp_weights = compute_rp_weights(prices, pool, active_mask, params) + _log_step("RPWeight", "风险平价权重", pool, rp_weights, fmt=".4f", etf_names=etf_names) + + # 4. 计算动量分数 + momentum_scores = compute_momentum_scores(prices, pool, trend_gates, params) + _log_step("MomentumScore", "动量分数", pool, momentum_scores, fmt=".4f", etf_names=etf_names) + + # 5. 动量倾斜乘数 + momentum_tilts = compute_momentum_tilt_multipliers(momentum_scores, trend_gates, params) + _log_step("MomentumTilt", "动量倾斜乘数", pool, momentum_tilts, fmt=".4f", etf_names=etf_names) + + # 6. RSRS 倾斜乘数 + rsrs_tilts = compute_rsrs_tilt_multipliers(prices, pool, trend_gates, params) + _log_step("RSRSTilt", "RSRS倾斜乘数", pool, rsrs_tilts, fmt=".4f", etf_names=etf_names) + + # 7. 合成倾斜权重(动量 + RSRS 同时参与相对倾斜,活跃资产内重新归一化) + tilted_weights = apply_relative_tilts(rp_weights, trend_gates, momentum_tilts, rsrs_tilts) + _log_step("TiltedWeight", "倾斜合成权重", pool, tilted_weights, fmt=".4f", etf_names=etf_names) + + # 8. 拥挤度线性惩罚乘数 + crowd_penalties = compute_crowd_penalties(prices, pool, params) + _log_step("CrowdPenalty", "拥挤度惩罚", pool, crowd_penalties, fmt=".4f", etf_names=etf_names) + + # 9. 合成 RawWeight(不重新归一化) + raw_weights = compose_raw_weights(tilted_weights, trend_gates, crowd_penalties) + + # 10. 组合波动率缩放 + portfolio_vol_asset_scales, portfolio_vol_meta = compute_portfolio_vol_asset_scales( + prices, pool, raw_weights, params + ) + portfolio_vol_scale = portfolio_vol_meta["base_scale"] + log.info("[组合波动率缩放] PortfolioVolScale=%.4f", portfolio_vol_scale) + + # 11. 最终权重 + final_weights = raw_weights * portfolio_vol_asset_scales + _log_step("FinalWeight", "最终权重", pool, final_weights, fmt=".4f", etf_names=etf_names) + + # 12. 应用交易约束 + final_weights_before_constraints = np.copy(final_weights) + final_weights = apply_weight_constraints(final_weights, params) + audit_event( + "rebalance_signals", + context, + pool=pool, + etf_names=etf_names, + params=params, + trend_gates=trend_gates, + rp_weights=rp_weights, + momentum_scores=momentum_scores, + momentum_tilts=momentum_tilts, + rsrs_tilts=rsrs_tilts, + tilted_weights=tilted_weights, + crowd_penalties=crowd_penalties, + raw_weights=raw_weights, + portfolio_vol_scale=portfolio_vol_scale, + portfolio_vol_relief_mode=portfolio_vol_meta["mode"], + portfolio_vol_ratio=portfolio_vol_meta["vol_ratio"], + portfolio_vol_base_scale=portfolio_vol_meta["base_scale"], + portfolio_vol_asset_scales=portfolio_vol_asset_scales, + portfolio_vol_relief_asset=portfolio_vol_meta["relief_asset"], + portfolio_vol_relief_weight=portfolio_vol_meta["relief_weight"], + portfolio_vol_relief_reason=portfolio_vol_meta["reason"], + final_weights_before_constraints=final_weights_before_constraints, + final_weights=final_weights, + execution_timing_mode=params["ExecutionTimingMode"], + asof_date=asof_date, + trade_date=_context_trade_date(context), + ) + + return { + "pool": pool, + "final_weights": final_weights, + "params": params, + "asof_date": asof_date, + "prepared_date": _context_trade_date(context), + } + + +def weekly_check(context): + """每周开盘时生成信号并立即执行调仓。""" + plan = build_rebalance_plan(context) + execute_rebalance(context, plan["pool"], plan["final_weights"], plan["params"]) + + +def prepare_delay_only_rebalance(context): + """按 baseline 口径先生成信号,延后到下一交易日开盘执行。""" + plan = build_rebalance_plan(context) + g.pending_rebalances.append(plan) + audit_event( + "rebalance_prepared", + context, + execution_timing_mode=plan["params"]["ExecutionTimingMode"], + asof_date=plan["asof_date"], + prepared_date=plan["prepared_date"], + final_weights=plan["final_weights"], + ) + + +def execute_pending_rebalance(context): + """执行 logic-2 中已缓存、且至少延后一交易日的调仓。""" + queue = getattr(g, "pending_rebalances", []) + if not queue: + return + + pending = queue[0] + trade_date = _context_trade_date(context) + prepared_date = pending.get("prepared_date") + if trade_date is None or prepared_date is None or trade_date <= prepared_date: + audit_event( + "pending_rebalance_wait", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + asof_date=pending.get("asof_date"), + prepared_date=prepared_date, + trade_date=trade_date, + ) + return + + audit_event( + "pending_rebalance_execute", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + asof_date=pending.get("asof_date"), + prepared_date=prepared_date, + trade_date=trade_date, + final_weights=pending["final_weights"], + ) + execute_rebalance(context, pending["pool"], pending["final_weights"], pending["params"]) + g.pending_rebalances.pop(0) + + +def mark_live_like_signal_day(context): + """记录本周首个交易日,供下一交易日开盘生成并执行 logic-3 信号。""" + signal_date = _context_trade_date(context) + g.pending_live_like_signal_days.append(signal_date) + audit_event( + "live_like_signal_marked", + context, + execution_timing_mode=snapshot_params()["ExecutionTimingMode"], + signal_date=signal_date, + ) + + +def execute_live_like_rebalance(context): + """在首个交易日之后的下一交易日开盘,生成并执行 logic-3 信号。""" + queue = getattr(g, "pending_live_like_signal_days", []) + if not queue: + return + + signal_date = queue[0] + trade_date = _context_trade_date(context) + if trade_date is None or signal_date is None or trade_date <= signal_date: + audit_event( + "live_like_wait", + context, + execution_timing_mode=snapshot_params()["ExecutionTimingMode"], + signal_date=signal_date, + trade_date=trade_date, + ) + return + + asof_date = getattr(context, "previous_date", None) + if asof_date != signal_date: + audit_event( + "live_like_skip", + context, + execution_timing_mode=snapshot_params()["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=asof_date, + trade_date=trade_date, + reason="previous_date_not_signal_date", + ) + queue.pop(0) + return + + audit_event( + "live_like_rebalance_execute", + context, + execution_timing_mode=snapshot_params()["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=asof_date, + trade_date=trade_date, + ) + weekly_check(context) + queue.pop(0) + + +def prepare_weekend_close_rebalance(context): + """收盘后生成候选信号 plan,次开盘跨周时执行。""" + signal_date = _context_trade_date(context) + params = snapshot_params() + if signal_date is None: + audit_event( + "weekend_close_signal_skip", + context, + execution_timing_mode=params["ExecutionTimingMode"], + signal_date=signal_date, + reason="signal_date_unavailable", + ) + return + + plan = build_rebalance_plan(context, asof_date=signal_date) + plan = _enrich_weekend_close_plan(context, plan, signal_date) + plan["signal_notice_sent"] = _report_signal_plan(plan) + g.pending_rebalances = [plan] + audit_event( + "weekend_close_plan_cached", + context, + execution_timing_mode=plan["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=signal_date, + batch_id=plan["batch_id"], + target_weights=plan["target_weights"], + target_values=plan["target_values"], + signal_notice_sent=plan["signal_notice_sent"], + plan_cached=True, + final_weights=plan["final_weights"], + ) + + +def execute_weekend_close_rebalance(context): + """次交易日开盘执行已缓存的收盘信号 plan,不重新计算信号。""" + queue = getattr(g, "pending_rebalances", []) + if not queue: + return + + pending = queue[0] + trade_date = _context_trade_date(context) + signal_date = pending.get("signal_date") + if trade_date is None or signal_date is None: + audit_event( + "weekend_close_execute_wait", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + trade_date=trade_date, + ) + return + if _same_iso_week(signal_date, trade_date): + audit_event( + "weekend_close_plan_discarded", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + trade_date=trade_date, + reason="not_last_trade_day_of_week", + ) + g.pending_rebalances.pop(0) + return + + pending["trade_date"] = trade_date + suppress_reason = "signal_notice_already_sent" if pending.get("signal_notice_sent") else "" + notice_suppressed = _suppress_execution_notice(pending.get("batch_id")) if suppress_reason else False + audit_event( + "weekend_close_execute", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + batch_id=pending.get("batch_id"), + trade_date=trade_date, + execution_order_called=True, + execution_notice_suppressed=notice_suppressed, + suppress_reason=suppress_reason if notice_suppressed else "", + final_weights=pending["final_weights"], + ) + try: + execute_rebalance(context, pending["pool"], pending["final_weights"], pending["params"]) + finally: + if notice_suppressed: + _resume_execution_notice() + g.pending_rebalances.pop(0) + + +# ============================================================ +# normalize_field_frame — 数据返回结构归一化 +# ============================================================ +def normalize_field_frame(raw, field, pool): + """ + 将 get_price 返回的单 ETF 结果归一化,辅助测试与本地诊断。 + + 处理规则: + - None 或空 DataFrame → 返回 columns=pool 的空 DataFrame + - 普通 DataFrame → reindex(columns=pool) 补全缺列 + """ + if raw is None: + return pd.DataFrame(columns=pool) + if not isinstance(raw, pd.DataFrame): + return pd.DataFrame(columns=pool) + if len(raw) == 0: + return pd.DataFrame(columns=pool) + + raw = raw.reindex(columns=pool) + return raw.dropna(how='all') + + +# ============================================================ +# compute_history_count — 计算所需历史数据长度 +# ============================================================ +def compute_history_count(params): + """ + 按模块显式计算所需历史数据长度。 + + 说明: + - 动量和收益率类计算需要多取 1 日 + - RSRS 至少需要 RSRS_M + RSRS_N - 1 + - buffer 用于容忍停牌、缺失值、上市初期数据不足 + """ + requirements = [ + max(resolve_ma_long_windows(params)), + max(params["MomShort"], params["MomMid"], params["MomLong"]) + 1, + params["VolWindow"] + 1, + params["RSRS_M"] + params["RSRS_N"] - 1, + params["CrowdWindow"], + params["PortfolioVolWindow"] + 1, + ] + return max(requirements) + params.get("history_buffer", 50) + + +# ============================================================ +# fetch_field + get_history_data — 拉取历史行情数据 +# ============================================================ +def fetch_field(pool, field, count, params, end_date=None): + """ + 逐 ETF 拉取单字段数据,返回 DataFrame(index=日期, columns=ETF代码)。 + + 单标的 + panel=False 在聚宽云端稳定返回 DataFrame(columns=字段名), + 多标的传入 panel=False 可能仍返回 Panel,因此改为逐只拉取后手工组装。 + + end_date: 历史数据截止日期。开盘调仓时需传入 context.previous_date + 以避免当日收盘价未来数据。 + """ + series_map = {} + for etf in pool: + df = get_price( + etf, + count=count, + end_date=end_date, + frequency='daily', + fields=[field], + skip_paused=True, + fq=params["fq_mode"], + panel=False, + ) + if df is not None and len(df) > 0: + series_map[etf] = df[field] + + if not series_map: + return pd.DataFrame(columns=pool) + + result = pd.DataFrame(series_map) + result = result.reindex(columns=pool) + return result.dropna(how='all') + + +def get_history_data(context, pool, params, asof_date=None): + """ + 拉取足够长的历史 OHLC + 成交额数据,并预计算日收益率。 + + 返回 dict: + close/high/low/amount: DataFrame(index=日期, columns=ETF代码) + close_ret: DataFrame(index=日期, columns=ETF代码),close 的 pct_change() + """ + needed = compute_history_count(params) + history_end_date = asof_date if asof_date is not None else getattr(context, "previous_date", None) + + prices = {} + prices['close'] = fetch_field(pool, 'close', needed, params, end_date=history_end_date) + prices['high'] = fetch_field(pool, 'high', needed, params, end_date=history_end_date) + prices['low'] = fetch_field(pool, 'low', needed, params, end_date=history_end_date) + prices['amount'] = fetch_field(pool, 'money', needed, params, end_date=history_end_date) + + # 预计算日收益率,下游风险平价和组合波动率复用同一份 + prices['close_ret'] = prices['close'].pct_change() + + # 数据新鲜度日志:确保历史数据不晚于 context.previous_date + close_df = prices['close'] + last_dt = close_df.index[-1] if len(close_df) else None + log.info("history end_date=%s, asof_date=%s", last_dt, history_end_date) + + return prices + + +# ============================================================ +# compute_trend_gates — 趋势门槛(硬过滤) +# ============================================================ +def compute_trend_gates(prices, pool, params): + """ + 使用趋势均线判断方向,支持按 ETF 分别设置窗口。 + + 返回: list[float],1.0 表示通过,0.0 表示剔除 + """ + close = prices['close'] + ma_windows = resolve_ma_long_windows(params) + + gates = np.zeros(len(pool)) + for i, etf in enumerate(pool): + if etf not in close.columns: + continue + ma_window = ma_windows[i] + series = close[etf].dropna() + if len(series) < ma_window: + continue + ma = series.iloc[-ma_window:].mean() + current_close = series.iloc[-1] + if current_close > ma: + gates[i] = 1.0 + return gates + + +# ============================================================ +# compute_momentum_scores — 多周期排名动量分数 +# ============================================================ +def compute_momentum_scores(prices, pool, trend_gates, params): + """ + 在趋势成立的资产中,计算多周期排名动量分数。 + + 对 close DataFrame 批量取各周期终点收益,再用 rank(pct=True) + 在截面上生成 0~1 排名分数,最后按权重线性组合。 + + 返回: np.array,未通过趋势门槛的资产分数为 0 + """ + close = prices['close'] + n = len(pool) + scores = np.zeros(n) + + windows = [params["MomShort"], params["MomMid"], params["MomLong"]] + period_weights = [params["w20"], params["w60"], params["w120"]] + + active_indices = [i for i in range(n) if trend_gates[i] > 0] + if not active_indices: + return scores + + active_pool = [pool[i] for i in active_indices if pool[i] in close.columns] + if not active_pool: + return scores + active_close = close[active_pool] + if len(active_close) <= max(windows): + return scores + + for j, w in enumerate(windows): + if len(active_close) <= w: + continue + latest = active_close.iloc[-1] + past = active_close.iloc[-(w + 1)] + period_ret = latest / past - 1 # pd.Series, index=ETF代码 + + # rank(pct=True) 将收益率映射到 [0, 1],收益率越高排名越接近 1 + ranks = period_ret.rank(pct=True).fillna(0.0) + + for idx_pos, active_i in enumerate(active_indices): + etf_code = pool[active_i] + scores[active_i] += period_weights[j] * float(ranks.get(etf_code, 0.0)) + + return scores + + +# ============================================================ +# select_topk — TopK 入选 +# ============================================================ +def select_topk(momentum_scores, trend_gates, params): + """ + 在趋势成立资产中按动量分数从高到低选择前 TopK 只。 + + 返回: list[bool],入选为 True + """ + n = len(momentum_scores) + selected = [False] * n + + active = [(i, momentum_scores[i]) for i in range(n) if trend_gates[i] > 0] + active.sort(key=lambda x: x[1], reverse=True) + + k = min(params["TopK"], len(active)) + for idx, _ in active[:k]: + selected[idx] = True + + return selected + + +# ============================================================ +# compute_rp_weights — 逆波动率风险平价 +# ============================================================ +def compute_rp_weights(prices, pool, active_mask, params): + """ + 对活跃资产计算逆波动率风险平价权重。 + + 所有趋势成立资产均参与基础权重计算,不再受 TopK 限制。 + 使用 get_history_data 预计算的 close_ret,避免重复 pct_change()。 + + 返回: np.array + """ + close_ret = prices['close_ret'] + n = len(pool) + vol_window = params["VolWindow"] + annual_factor = params["annual_factor"] + + weights = np.zeros(n) + active_indices = [i for i in range(n) if active_mask[i]] + + if not active_indices: + return weights + + vols = np.zeros(n) + for i in active_indices: + etf = pool[i] + if etf not in close_ret.columns: + continue + daily_ret = close_ret[etf].dropna().iloc[-vol_window:] + if len(daily_ret) < 5: + vols[i] = 1.0 + else: + vols[i] = daily_ret.std() * np.sqrt(annual_factor) + if vols[i] < 1e-8: + vols[i] = 1e-8 + + inverse_vols = np.zeros(n) + for i in active_indices: + if vols[i] > 0: + inverse_vols[i] = 1.0 / vols[i] + + total_inv_vol = inverse_vols.sum() + if total_inv_vol > 0: + weights = inverse_vols / total_inv_vol + + return weights + + +# ============================================================ +# compute_rsrs_multipliers — RSRS 线性修正乘数 +# ============================================================ +def compute_rsrs_multipliers(prices, pool, params): + """ + 对每只 ETF 计算 RSRS 截断线性乘数(旧接口,保留兼容)。 + + 委托 compute_rsrs_adjusted_scores 获取原始信号, + 再按旧公式 clip(1 + RSRS_Adj / NegativeFullCut, Min, Max)。 + + 只减仓,不加仓。 + """ + rsrs_adj = compute_rsrs_adjusted_scores(prices, pool, params) + full_cut = params["RSRS_NegativeFullCut"] + n = len(pool) + + multipliers = np.ones(n) + for i in range(n): + raw_mult = 1.0 + rsrs_adj[i] / full_cut + multipliers[i] = np.clip(raw_mult, params["RSRSMinMultiplier"], params["RSRSMaxMultiplier"]) + + return multipliers + + +# ============================================================ +# compute_rsrs_adjusted_scores — RSRS 原始结构信号 +# ============================================================ +def compute_rsrs_adjusted_scores(prices, pool, params): + """ + 计算每只 ETF 的 RSRS 原始调整信号:RSRSAdj_i = RSRS_Z_i × R2_i。 + + 返回: np.array,数据不足时对应位置为 0。 + """ + high = prices['high'] + low = prices['low'] + n = len(pool) + + N = params["RSRS_N"] + M = params["RSRS_M"] + + scores = np.zeros(n) + + for i, etf in enumerate(pool): + if etf not in high.columns or etf not in low.columns: + continue + h = high[etf].dropna() + l = low[etf].dropna() + + common_idx = h.index.intersection(l.index) + h = h.loc[common_idx] + l = l.loc[common_idx] + + min_len = M + N - 1 + if len(h) < min_len: + continue + + h_roll = h.rolling(N) + l_roll = l.rolling(N) + cov_vals = h_roll.cov(l).dropna().values + var_l_vals = l_roll.var().dropna().values + var_h_vals = h_roll.var().dropna().values + + betas = cov_vals / var_l_vals + r2s = cov_vals ** 2 / (var_h_vals * var_l_vals) + + bad = ( + (var_l_vals < 1e-10) | (var_h_vals < 1e-10) + | (~np.isfinite(betas)) | (~np.isfinite(r2s)) + ) + betas[bad] = 1.0 + r2s[bad] = 0.0 + + if len(betas) < M: + continue + + beta_series = betas[-M:] + mean_beta = np.mean(beta_series) + std_beta = np.std(beta_series) + + if std_beta < 1e-10: + rsrs_z = 0.0 + else: + rsrs_z = (beta_series[-1] - mean_beta) / std_beta + + latest_r2 = r2s[-1] + scores[i] = rsrs_z * latest_r2 + + return scores + + +# ============================================================ +# compute_momentum_tilt_multipliers — 动量相对倾斜乘数 +# ============================================================ +def compute_momentum_tilt_multipliers(momentum_scores, trend_gates, params): + """ + 将动量分数转换为资产间相对倾斜乘数。 + + 活跃资产围绕均值上下倾斜,非活跃资产返回 0。 + 倾斜公式:clip(1 + strength × (score_i - mean_active), min, max) + 若启用极端高动量弱化,则 score_i 达到阈值后再将高位倾斜压到 cap。 + """ + n = len(momentum_scores) + tilts = np.zeros(n) + + active_indices = [i for i in range(n) if trend_gates[i] > 0] + if not active_indices: + return tilts + + active_scores = momentum_scores[active_indices] + mean_score = np.mean(active_scores) + + strength = params["MomentumTiltStrength"] + tilt_min = params["MomentumTiltMin"] + tilt_max = params["MomentumTiltMax"] + extreme_start = params["MomentumExtremeScoreStart"] + extreme_cap = params["MomentumExtremeTiltCap"] + + for i in active_indices: + edge = momentum_scores[i] - mean_score + raw_tilt = 1.0 + strength * edge + tilt = np.clip(raw_tilt, tilt_min, tilt_max) + if extreme_start is not None and momentum_scores[i] >= extreme_start: + tilt = min(tilt, extreme_cap) + tilts[i] = tilt + + return tilts + + +# ============================================================ +# compute_rsrs_tilt_multipliers — RSRS 相对倾斜乘数 +# ============================================================ +def compute_rsrs_tilt_multipliers(prices, pool, trend_gates, params): + """ + 计算 RSRS 原始结构信号,并转换为资产间相对倾斜乘数。 + + 活跃资产围绕均值上下倾斜,非活跃资产返回 0。 + 倾斜公式:clip(1 + (RSRSAdj_i - mean_active) / NegativeFullCut, min, max) + """ + rsrs_adj = compute_rsrs_adjusted_scores(prices, pool, params) + n = len(pool) + tilts = np.zeros(n) + + active_indices = [i for i in range(n) if trend_gates[i] > 0] + if not active_indices: + return tilts + + active_adj = rsrs_adj[active_indices] + mean_adj = np.mean(active_adj) + + full_cut = params["RSRS_NegativeFullCut"] + tilt_min = params["RSRSTiltMin"] + tilt_max = params["RSRSTiltMax"] + + for i in active_indices: + edge = rsrs_adj[i] - mean_adj + raw_tilt = 1.0 + edge / full_cut + tilts[i] = np.clip(raw_tilt, tilt_min, tilt_max) + + return tilts + + +# ============================================================ +# apply_relative_tilts — 倾斜权重合成与归一化 +# ============================================================ +def apply_relative_tilts(rp_weights, trend_gates, momentum_tilts, rsrs_tilts): + """ + 将风险平价基础权重、动量倾斜和 RSRS 倾斜合成,并在活跃资产内归一化。 + + tilted_raw_i = RPWeight_i × MomentumTilt_i × RSRSTilt_i + 归一化到 sum(RPWeight_active),非活跃资产权重为 0。 + """ + n = len(rp_weights) + tilted = np.zeros(n) + + active_indices = [i for i in range(n) if trend_gates[i] > 0 and rp_weights[i] > 0] + if not active_indices: + return tilted + + tilted_raw = np.zeros(n) + for i in active_indices: + tilted_raw[i] = rp_weights[i] * momentum_tilts[i] * rsrs_tilts[i] + + total_raw = tilted_raw[active_indices].sum() + base_total = rp_weights[active_indices].sum() + + if total_raw <= 0 or base_total <= 0: + return np.copy(rp_weights) + + for i in active_indices: + tilted[i] = tilted_raw[i] / total_raw * base_total + + return tilted + + +# ============================================================ +# compute_crowd_penalties — 拥挤度线性惩罚乘数 +# ============================================================ +def compute_crowd_penalties(prices, pool, params): + """ + 对每只 ETF 计算拥挤度线性惩罚乘数。 + + 先在 DataFrame 级批量计算五类指标(ret20/ret60/amt_ma20/deviation/vol20), + 再逐 ETF 取最后一行做分位排名,减少 Python 层逐列 rolling/pct_change 开销。 + + 只减仓,不加仓。 + + 返回: np.array + """ + close = prices['close'] + amount = prices['amount'] + n = len(pool) + + crowd_window = params["CrowdWindow"] + thresholds = resolve_crowd_thresholds(params) # [(start, end, min_penalty), ...] + ret_windows = resolve_crowd_ret_windows(params) # [(short, mid), ...] + + penalties = np.ones(n) + + # 过滤数据不足的 ETF + eligible_etfs = [] + for i, etf in enumerate(pool): + if etf in close.columns and len(close[etf].dropna()) >= crowd_window: + eligible_etfs.append(etf) + else: + penalties[i] = 1.0 + + if not eligible_etfs: + return penalties + + # ---- DataFrame 级批量计算:一次算完所有 ETF 的指标 ---- + close_recent = close[eligible_etfs].iloc[-crowd_window:] + + # 1-2) 短/中期涨幅(按 per-ETF 窗口预计算,避免重复 shift) + ret_short_map = {} + ret_mid_map = {} + for i, etf in enumerate(pool): + if etf not in eligible_etfs: + continue + short_w, mid_w = ret_windows[i] + if short_w not in ret_short_map: + ret_short_map[short_w] = close_recent / close_recent.shift(short_w) - 1 + if mid_w not in ret_mid_map: + ret_mid_map[mid_w] = close_recent / close_recent.shift(mid_w) - 1 + + # 3) 成交额 MA20(仅对有 amount 数据的 ETF) + eligible_amount_cols = [e for e in eligible_etfs if e in amount.columns] + amt_ma20_df = None + if eligible_amount_cols: + amt_aligned = amount[eligible_amount_cols].loc[ + amount.index.intersection(close_recent.index) + ] + if len(amt_aligned) >= params["AmountMAWindow"]: + amt_ma20_df = amt_aligned.rolling(params["AmountMAWindow"]).mean() + + # 4) 偏离均线程度 + ma20_df = close_recent.rolling(params["DeviationMAWindow"]).mean() + deviation_df = close_recent / ma20_df - 1 + + # 5) 短期波动率 + vol20_df = close_recent.pct_change().rolling(params["CrowdVolWindow"]).std() * np.sqrt(params["annual_factor"]) + + # ---- 逐 ETF 从预计算 DataFrame 中取列做分位排名 ---- + for i, etf in enumerate(pool): + if etf not in eligible_etfs: + continue + + indicators = [] + + # ret short (per-ETF window) + short_w = ret_windows[i][0] + col_short = ret_short_map[short_w][etf].dropna() + indicators.append(percentile_rank(col_short.iloc[-1], col_short) if len(col_short) > 1 else 0.5) + + # ret mid (per-ETF window) + mid_w = ret_windows[i][1] + col_mid = ret_mid_map[mid_w][etf].dropna() + indicators.append(percentile_rank(col_mid.iloc[-1], col_mid) if len(col_mid) > 1 else 0.5) + + # amt_ma20 + if amt_ma20_df is not None and etf in amt_ma20_df.columns: + col_amt = amt_ma20_df[etf].dropna() + indicators.append(percentile_rank(col_amt.iloc[-1], col_amt) if len(col_amt) > 1 else 0.5) + else: + indicators.append(0.5) + + # deviation + col_dev = deviation_df[etf].dropna() + indicators.append(percentile_rank(col_dev.iloc[-1], col_dev) if len(col_dev) > 1 else 0.5) + + # vol20 + col_vol = vol20_df[etf].dropna() + indicators.append(percentile_rank(col_vol.iloc[-1], col_vol) if len(col_vol) > 1 else 0.5) + + crowd_score = np.mean(indicators) + + etf_start, etf_end, etf_min = thresholds[i] + if crowd_score <= etf_start: + penalty = 1.0 + elif crowd_score >= etf_end: + penalty = etf_min + else: + penalty = 1.0 - (crowd_score - etf_start) / (etf_end - etf_start) * (1.0 - etf_min) + penalty = max(etf_min, min(1.0, penalty)) + + penalties[i] = penalty + + return penalties + + +# ============================================================ +# percentile_rank — 计算分位数排名(0~1) +# ============================================================ +def percentile_rank(value, series): + """ + 计算 value 在 series 中的分位数(0~1)。 + + 返回 0 表示 value 是序列中最小值,返回 1 表示最大值。 + """ + if len(series) == 0: + return 0.5 + ranked = (series < value).mean() + return float(ranked) + + +# ============================================================ +# compute_portfolio_vol_scale — 组合波动率缩放系数 +# ============================================================ +def compute_portfolio_vol_scale_detail(prices, pool, raw_weights, params): + """ + 根据 RawWeight 和协方差矩阵计算组合波动率,按目标波动率缩放。 + + 使用 get_history_data 预计算的 close_ret,避免重复 pct_change()。 + 只缩不放(最大系数为 1.0)。 + + 返回: (scale, vol_ratio) + """ + close_ret = prices['close_ret'] + vol_window = params["PortfolioVolWindow"] + target_vol = params["TargetVol"] + annual_factor = params["annual_factor"] + + n = len(pool) + active_indices = [i for i in range(n) if raw_weights[i] > 1e-8] + + if not active_indices: + return 1.0, None + + returns_list = [] + for i in active_indices: + etf = pool[i] + if etf not in close_ret.columns: + return 1.0, None + ret = close_ret[etf].dropna().iloc[-vol_window:] + if len(ret) < vol_window: + return 1.0, None + returns_list.append(ret.values) + + if not returns_list: + return 1.0, None + + ret_matrix = np.column_stack(returns_list) + cov_daily = np.atleast_2d(np.cov(ret_matrix, rowvar=False)) + cov_annual = cov_daily * annual_factor + + active_weights = np.array([raw_weights[i] for i in active_indices]) + portfolio_var = active_weights @ cov_annual @ active_weights + portfolio_vol = np.sqrt(max(portfolio_var, 0)) + vol_ratio = float(portfolio_vol / target_vol) + + if portfolio_vol <= target_vol or portfolio_vol < 1e-8: + return 1.0, vol_ratio + + scale = target_vol / portfolio_vol + return min(scale, params["MaxPortfolioVolScale"]), vol_ratio + + +def compute_portfolio_vol_scale(prices, pool, raw_weights, params): + """返回原有组合级波动率缩放标量。""" + scale, _vol_ratio = compute_portfolio_vol_scale_detail(prices, pool, raw_weights, params) + return scale + + +def compute_portfolio_vol_asset_scales(prices, pool, raw_weights, params): + """返回每只 ETF 的组合波控缩放系数和审计元数据。""" + base_scale, vol_ratio = compute_portfolio_vol_scale_detail(prices, pool, raw_weights, params) + asset_scales = np.full(len(pool), base_scale) + mode = params["PortfolioVolReliefMode"] + meta = { + "mode": mode, + "vol_ratio": vol_ratio, + "base_scale": base_scale, + "relief_asset": None, + "relief_weight": 0.0, + "reason": "baseline", + } + if mode == "fixed_gold": + return apply_fixed_gold_vol_relief(pool, raw_weights, asset_scales, meta, params) + if mode == "dyn_marginal": + return apply_dynamic_marginal_vol_relief(prices, pool, raw_weights, asset_scales, meta, params) + return asset_scales, meta + + +def apply_fixed_gold_vol_relief(pool, raw_weights, asset_scales, meta, params): + """固定黄金弱缩放:按参数恢复黄金被组合波控压掉的部分仓位。""" + gold_code = "518880.XSHG" + vol_ratio = meta["vol_ratio"] + base_scale = meta["base_scale"] + + if vol_ratio is None or vol_ratio <= 1.0: + meta["reason"] = "vol_not_above_target" + return asset_scales, meta + if vol_ratio > params["GoldVolReliefMaxRatio"]: + meta["reason"] = "ratio_too_high" + return asset_scales, meta + if gold_code not in pool: + meta["reason"] = "gold_not_in_pool" + return asset_scales, meta + + gold_idx = pool.index(gold_code) + if raw_weights[gold_idx] <= 1e-8: + meta["reason"] = "gold_not_active" + return asset_scales, meta + + new_scale = min(1.0, base_scale + (1.0 - base_scale) * params["GoldVolReliefFraction"]) + asset_scales[gold_idx] = new_scale + meta["relief_asset"] = gold_code + meta["relief_weight"] = float(raw_weights[gold_idx] * (new_scale - base_scale)) + meta["reason"] = "fixed_gold" + return asset_scales, meta + + +def apply_dynamic_marginal_vol_relief(prices, pool, raw_weights, asset_scales, meta, params): + """动态弱缩放:在正动量持仓中选择边际风险最低资产恢复仓位。""" + vol_ratio = meta["vol_ratio"] + base_scale = meta["base_scale"] + + if vol_ratio is None or vol_ratio <= 1.0: + meta["reason"] = "vol_not_above_target" + return asset_scales, meta + if vol_ratio > params["DynamicVolReliefMaxRatio"]: + meta["reason"] = "ratio_too_high" + return asset_scales, meta + + selected_idx, reason = select_dynamic_marginal_relief_asset( + prices, pool, raw_weights, params + ) + if selected_idx is None: + meta["reason"] = reason + return asset_scales, meta + + new_scale = min(1.0, base_scale + (1.0 - base_scale) * params["DynamicVolReliefFraction"]) + asset_scales[selected_idx] = new_scale + meta["relief_asset"] = pool[selected_idx] + meta["relief_weight"] = float(raw_weights[selected_idx] * (new_scale - base_scale)) + meta["reason"] = "selected_low_marginal_risk" + return asset_scales, meta + + +def select_dynamic_marginal_relief_asset(prices, pool, raw_weights, params): + """返回动态弱缩放资产下标和原因。""" + active_indices = [i for i, weight in enumerate(raw_weights) if weight > 1e-8] + if not active_indices: + return None, "no_active_asset" + + close_ret = prices.get("close_ret") + if close_ret is None: + return None, "insufficient_cov_data" + + active_codes = [pool[i] for i in active_indices] + for code in active_codes: + if code not in close_ret.columns: + return None, "insufficient_cov_data" + + cov_window = params["DynamicVolReliefCovWindow"] + active_returns = close_ret[active_codes].dropna().iloc[-cov_window:] + if len(active_returns) < cov_window: + return None, "insufficient_cov_data" + + momentum_window = params["DynamicVolReliefMomentumWindow"] + positive_candidates = [] + for active_pos, code in enumerate(active_codes): + momentum_ret = close_ret[code].dropna().iloc[-momentum_window:] + if len(momentum_ret) < momentum_window: + continue + momentum = float(np.prod(1.0 + momentum_ret.values) - 1.0) + if momentum >= 0.0: + positive_candidates.append(active_pos) + + if not positive_candidates: + return None, "no_positive_momentum_asset" + + cov_daily = np.atleast_2d(np.cov(active_returns.values, rowvar=False)) + cov_annual = cov_daily * params["annual_factor"] + active_weights = np.array([raw_weights[i] for i in active_indices]) + marginal_scores = cov_annual @ active_weights + best_active_pos = min( + positive_candidates, + key=lambda active_pos: marginal_scores[active_pos], + ) + return active_indices[best_active_pos], "selected_low_marginal_risk" + + +# ============================================================ +# apply_weight_constraints — 应用仓位约束 +# ============================================================ +def apply_weight_constraints(final_weights, params): + """ + 应用单资产最大仓位、最小有效仓位和总仓位上限约束。 + + 总仓位约束在单资产约束之后应用,缩放后不重新归一化。 + """ + max_w = params["MaxWeight"] + min_w = params["MinWeight"] + max_total = params["MaxTotalWeight"] + n = len(final_weights) + + result = np.copy(final_weights) + + for i in range(n): + if result[i] > max_w: + result[i] = max_w + if result[i] < min_w: + result[i] = 0.0 + + total = result.sum() + if total > max_total: + result = result * max_total / total + + return result + + +# ============================================================ +# execute_rebalance — 执行调仓 +# ============================================================ +def execute_rebalance(context, pool, final_weights, params): + """ + 根据最终目标权重执行调仓,应用最小调仓阈值。 + 剩余仓位保留为现金。 + + 执行前检查停牌状态,执行后记录订单结果,便于审计和故障定位。 + """ + account_value = context.portfolio.total_value + current_data = get_current_data() + etf_names = build_etf_display_names(pool, params.get("etf_names")) + + for i, etf in enumerate(pool): + etf_name = etf_names[i] + target_value = account_value * final_weights[i] + current_pos = context.portfolio.positions[etf] + current_value = current_pos.total_amount * current_pos.price if current_pos.total_amount > 0 else 0 + current_weight = current_value / account_value if account_value > 0 else 0 + + # 如果目标权重为 0 且当前仓位为 0,跳过 + if final_weights[i] == 0 and current_weight == 0: + audit_event( + "rebalance_order", + context, + action="skip_zero_target_zero_position", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + ) + continue + + # 最小调仓阈值 + if abs(final_weights[i] - current_weight) < params["RebalanceThreshold"]: + audit_event( + "rebalance_order", + context, + action="skip_threshold", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + rebalance_threshold=params["RebalanceThreshold"], + ) + continue + + # 停牌检查 + data = current_data[etf] + if data.paused: + log.warning("skip paused ETF: %s security=%s", etf_name, etf) + audit_event( + "rebalance_order", + context, + action="skip_paused", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + ) + continue + + order_obj = order_target_value(etf, target_value) + if order_obj is None: + log.error( + "order failed: %s security=%s target_value=%.2f target_weight=%.4f current_weight=%.4f", + etf_name, etf, target_value, final_weights[i], current_weight + ) + audit_event( + "rebalance_order", + context, + action="order_failed", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + ) + else: + log.info( + "order sent: %s security=%s target_weight=%.4f current_weight=%.4f target_value=%.2f", + etf_name, etf, final_weights[i], current_weight, target_value + ) + audit_event( + "rebalance_order", + context, + action="order_sent", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + order=order_obj, + ) diff --git a/strategies/etf_factor_rotation/variants/code/weekend_close_signal_next_open_variant.py b/strategies/etf_factor_rotation/variants/code/weekend_close_signal_next_open_variant.py new file mode 100644 index 00000000..06a6cb54 --- /dev/null +++ b/strategies/etf_factor_rotation/variants/code/weekend_close_signal_next_open_variant.py @@ -0,0 +1,2025 @@ +# 策略名:ETF多因子轮动 +enable_profile() +STRATEGY_NAME = "ETF多因子轮动" +try: + import FeishuRelayTools +except ImportError: + FeishuRelayTools = None + +""" +============================================================ +策略名称:ETF 多因子轮动策略(相对倾斜版) +策略类型:周线级别、场内基金、多因子动态配置 +适用标的:159819.XSHE、513100.XSHG、518880.XSHG(中文名运行时通过聚宽 API 读取) + +核心思想: + 趋势门槛判断"能不能买",风险平价分配基础仓位, + 动量和 RSRS 作为资产间相对倾斜信号调整权重分配(不改变组合总仓位), + 拥挤度惩罚和组合波动率控制在过热或高波时平滑降仓。 + 剩余仓位保留现金,不重新归一化到满仓。 + +模块分工: + - 趋势门槛(硬过滤):按 ETF 专属趋势均线以上才可入选,0/1 离散 + - 风险平价(逆波动率):所有趋势成立资产参与,σ 越小权重越大 + - 动量倾斜(相对信号):多周期排名分数去均值,clip 到 [TiltMin, TiltMax]; + 可选将极端高分资产的动量倾斜压回中性 + - RSRS 倾斜(相对信号):High~Low 回归 β 标准化 × R² 去均值,clip 到 [TiltMin, TiltMax] + - 倾斜合成:RPWeight × MomentumTilt × RSRSTilt,活跃资产内重新归一化 + - 拥挤度惩罚(只减不加):五指标分位数均值,超阈值线性打折 + - 组合波动率控制(只缩不放):RawWeight 组合波动率超目标时等比缩放 + +核心公式: + TiltedWeight_i = normalize(RPWeight_i × MomentumTilt_i × RSRSTilt_i) + RawWeight_i = TiltedWeight_i × TrendGate_i × CrowdPenalty_i + FinalWeight_i = RawWeight_i × PortfolioVolAssetScale_i + +调仓频率:每周开盘检查一次 +============================================================ +""" + +import json +from datetime import date, datetime + +import numpy as np +import pandas as pd + + +# ============================================================ +# 常量 — 内部字段名到聚宽字段名的映射 +# ============================================================ +FIELD_MAP = { + "close": "close", + "high": "high", + "low": "low", + "amount": "money", +} + +JQ_AUTO_AUDIT_TOKEN = "manual" +JQ_AUTO_AUDIT_DIR = "jq_auto_audit" +PARAM_DEFAULTS = { + "etf_pool": ( + "159819.XSHE", + "513100.XSHG", + "518880.XSHG", + ), + "MA_long": 120, + "MA_long_by_etf": (20, 40, 100), + "MomShort": 20, + "MomMid": 60, + "MomLong": 120, + "w20": 0.2, + "w60": 0.3, + "w120": 0.5, + "TopK": 3, + "VolWindow": 60, + "annual_factor": 252, + "RSRS_N": 18, + "RSRS_M": 600, + "RSRS_NegativeFullCut": 1.8, + "RSRSMinMultiplier": 0.0, + "RSRSMaxMultiplier": 1.3, + "MomentumTiltStrength": 0.50, + "MomentumTiltMin": 0.70, + "MomentumTiltMax": 1.30, + "MomentumExtremeScoreStart": None, + "MomentumExtremeTiltCap": 1.00, + "RSRSTiltMin": 0.70, + "RSRSTiltMax": 1.30, + "CrowdWindow": 500, + "CrowdRetShort": 20, + "CrowdRetMid": 60, + "AmountMAWindow": 20, + "DeviationMAWindow": 20, + "CrowdVolWindow": 20, + "CrowdStart": 0.60, + "CrowdEnd": 0.95, + "MinCrowdPenalty": 0.30, + "CrowdStart_by_etf": (0.60, 0.60, 0.80), + "CrowdEnd_by_etf": None, + "MinCrowdPenalty_by_etf": None, + "CrowdRetShort_by_etf": None, + "CrowdRetMid_by_etf": None, + "PortfolioVolWindow": 40, + "TargetVol": 0.08, + "MaxPortfolioVolScale": 1.0, + "PortfolioVolReliefMode": "dyn_marginal", + "GoldVolReliefFraction": 0.5, + "GoldVolReliefMaxRatio": 2.0, + "DynamicVolReliefFraction": 1.0, + "DynamicVolReliefMaxRatio": 1.5, + "DynamicVolReliefMomentumWindow": 20, + "DynamicVolReliefCovWindow": 40, + "MaxWeight": 0.60, + "MinWeight": 0.05, + "RebalanceThreshold": 0.03, + "MaxTotalWeight": 1.0, + "ExecutionTimingMode": "weekend-close-signal-next-open", + "use_real_price": False, + "fq_mode": None, + "history_buffer": 100, + "benchmark": "000300.XSHG", +} +EXECUTION_TIMING_MODES = ( + "baseline", + "logic-2-delay-only", + "logic-3-live-like", + "weekend-close-signal-next-open", +) +DEFAULT_EXECUTION_TIMING_MODE = PARAM_DEFAULTS["ExecutionTimingMode"] +PORTFOLIO_VOL_RELIEF_MODES = ( + "baseline", + "fixed_gold", + "dyn_marginal", +) +PORTFOLIO_VOL_RELIEF_DEFAULTS = { + "PortfolioVolReliefMode": PARAM_DEFAULTS["PortfolioVolReliefMode"], + "GoldVolReliefFraction": PARAM_DEFAULTS["GoldVolReliefFraction"], + "GoldVolReliefMaxRatio": PARAM_DEFAULTS["GoldVolReliefMaxRatio"], + "DynamicVolReliefFraction": PARAM_DEFAULTS["DynamicVolReliefFraction"], + "DynamicVolReliefMaxRatio": PARAM_DEFAULTS["DynamicVolReliefMaxRatio"], + "DynamicVolReliefMomentumWindow": PARAM_DEFAULTS["DynamicVolReliefMomentumWindow"], + "DynamicVolReliefCovWindow": PARAM_DEFAULTS["DynamicVolReliefCovWindow"], +} + + +def fund_code(security): + """从聚宽证券代码中提取 6 位基金代码,用于报告和日志展示。""" + return str(security).split(".")[0] + + +def format_etf_name(security, name): + """保证基金显示名使用 中文名(聚宽代码) 标准格式。""" + security = str(security) + code = fund_code(security) + display_name = str(name).strip() if name is not None else str(security) + full_suffix = "(%s)" % security + short_suffix = "(%s)" % code + if display_name == security: + return display_name + if display_name.endswith(full_suffix): + return display_name + if code and display_name.endswith(short_suffix): + display_name = display_name[:-len(short_suffix)].strip() + return "%s%s" % (display_name, full_suffix) + + +def build_etf_display_names(pool, names=None): + """按 etf_pool 顺序生成带编号的基金显示名列表。""" + names = names or [] + result = [] + for i, etf in enumerate(pool): + base_name = names[i] if i < len(names) else etf + result.append(format_etf_name(etf, base_name)) + return result + + +def fetch_etf_official_name(security, fallback_name=None): + """通过聚宽 API 读取基金官方中文名,失败时回退到已有名称或代码。""" + try: + info = get_security_info(security) + for attr in ("display_name", "name"): + value = getattr(info, attr, None) + if value: + return str(value).strip() + except Exception as exc: + log.warning("fetch ETF official name failed: security=%s error=%s", security, exc) + + return fallback_name or str(security) + + +def load_etf_display_names(pool, fallback_names=None): + """从聚宽官方证券信息生成标准 ETF 显示名。""" + fallback_names = fallback_names or [] + names = [] + for i, etf in enumerate(pool): + fallback_name = fallback_names[i] if i < len(fallback_names) else None + official_name = fetch_etf_official_name(etf, fallback_name=fallback_name) + names.append(format_etf_name(etf, official_name)) + return names + + +def _audit_jsonable(value): + """Convert strategy/runtime values to JSON-safe audit payloads.""" + if value is None or isinstance(value, (str, bool, int, float)): + return value + if isinstance(value, (date, datetime)): + return value.isoformat() + try: + if isinstance(value, np.generic): + return _audit_jsonable(value.item()) + if isinstance(value, np.ndarray): + return [_audit_jsonable(item) for item in value.tolist()] + except Exception: + pass + try: + if isinstance(value, pd.Timestamp): + return value.isoformat() + if isinstance(value, (pd.Series, pd.Index)): + return [_audit_jsonable(item) for item in value.tolist()] + if isinstance(value, pd.DataFrame): + return [ + {str(k): _audit_jsonable(v) for k, v in row.items()} + for row in value.reset_index().to_dict(orient="records") + ] + except Exception: + pass + if isinstance(value, dict): + return {str(k): _audit_jsonable(v) for k, v in value.items()} + if isinstance(value, (list, tuple, set)): + return [_audit_jsonable(item) for item in value] + return str(value) + + +def _context_time_fields(context): + result = {} + for name in ("current_dt", "previous_date"): + value = getattr(context, name, None) if context is not None else None + if value is not None: + result[name] = _audit_jsonable(value) + return result + + +def audit_event(event, context=None, **payload): + """Write one complete business-audit event to JoinQuant research storage.""" + path = getattr(g, "audit_path", "") + if not path: + return + seq = getattr(g, "audit_seq", 0) + 1 + g.audit_seq = seq + row = { + "seq": seq, + "event": event, + "audit_token": getattr(g, "audit_token", ""), + } + row.update(_context_time_fields(context)) + row.update({key: _audit_jsonable(value) for key, value in payload.items()}) + write_file(path, json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n", append=True) + + +def _copy_runtime_default(value): + if isinstance(value, tuple): + return list(value) + if isinstance(value, list): + return list(value) + if isinstance(value, dict): + return dict(value) + return value + + +def _runtime_param(name, default): + if not hasattr(g, name): + setattr(g, name, _copy_runtime_default(default)) + return getattr(g, name) + + +def _runtime_list_param(name): + value = _runtime_param(name, PARAM_DEFAULTS[name]) + if value is None: + return None + return list(value) + + +# ============================================================ +# snapshot_params — 参数快照 +# ============================================================ +def snapshot_params(): + """ + 从 g 读取全部策略参数,返回只读快照 dict。 + + 核心计算函数通过接收 params 而非直接读 g,实现解耦。 + """ + etf_pool = _runtime_list_param("etf_pool") + raw_etf_names = _runtime_param("etf_names", etf_pool) + etf_names = build_etf_display_names(etf_pool, list(raw_etf_names)) + return { + "etf_pool": etf_pool, + "etf_names": etf_names, + "benchmark": _runtime_param("benchmark", PARAM_DEFAULTS["benchmark"]), + "MA_long": _runtime_param("MA_long", PARAM_DEFAULTS["MA_long"]), + "MA_long_by_etf": _runtime_list_param("MA_long_by_etf"), + "MomShort": _runtime_param("MomShort", PARAM_DEFAULTS["MomShort"]), + "MomMid": _runtime_param("MomMid", PARAM_DEFAULTS["MomMid"]), + "MomLong": _runtime_param("MomLong", PARAM_DEFAULTS["MomLong"]), + "w20": _runtime_param("w20", PARAM_DEFAULTS["w20"]), + "w60": _runtime_param("w60", PARAM_DEFAULTS["w60"]), + "w120": _runtime_param("w120", PARAM_DEFAULTS["w120"]), + "TopK": _runtime_param("TopK", PARAM_DEFAULTS["TopK"]), + "VolWindow": _runtime_param("VolWindow", PARAM_DEFAULTS["VolWindow"]), + "annual_factor": _runtime_param("annual_factor", PARAM_DEFAULTS["annual_factor"]), + "RSRS_N": _runtime_param("RSRS_N", PARAM_DEFAULTS["RSRS_N"]), + "RSRS_M": _runtime_param("RSRS_M", PARAM_DEFAULTS["RSRS_M"]), + "RSRS_NegativeFullCut": _runtime_param("RSRS_NegativeFullCut", PARAM_DEFAULTS["RSRS_NegativeFullCut"]), + "RSRSMinMultiplier": _runtime_param("RSRSMinMultiplier", PARAM_DEFAULTS["RSRSMinMultiplier"]), + "RSRSMaxMultiplier": _runtime_param("RSRSMaxMultiplier", PARAM_DEFAULTS["RSRSMaxMultiplier"]), + "MomentumTiltStrength": _runtime_param("MomentumTiltStrength", PARAM_DEFAULTS["MomentumTiltStrength"]), + "MomentumTiltMin": _runtime_param("MomentumTiltMin", PARAM_DEFAULTS["MomentumTiltMin"]), + "MomentumTiltMax": _runtime_param("MomentumTiltMax", PARAM_DEFAULTS["MomentumTiltMax"]), + "MomentumExtremeScoreStart": _runtime_param("MomentumExtremeScoreStart", PARAM_DEFAULTS["MomentumExtremeScoreStart"]), + "MomentumExtremeTiltCap": _runtime_param("MomentumExtremeTiltCap", PARAM_DEFAULTS["MomentumExtremeTiltCap"]), + "RSRSTiltMin": _runtime_param("RSRSTiltMin", PARAM_DEFAULTS["RSRSTiltMin"]), + "RSRSTiltMax": _runtime_param("RSRSTiltMax", PARAM_DEFAULTS["RSRSTiltMax"]), + "CrowdWindow": _runtime_param("CrowdWindow", PARAM_DEFAULTS["CrowdWindow"]), + "CrowdRetShort": _runtime_param("CrowdRetShort", PARAM_DEFAULTS["CrowdRetShort"]), + "CrowdRetMid": _runtime_param("CrowdRetMid", PARAM_DEFAULTS["CrowdRetMid"]), + "AmountMAWindow": _runtime_param("AmountMAWindow", PARAM_DEFAULTS["AmountMAWindow"]), + "DeviationMAWindow": _runtime_param("DeviationMAWindow", PARAM_DEFAULTS["DeviationMAWindow"]), + "CrowdVolWindow": _runtime_param("CrowdVolWindow", PARAM_DEFAULTS["CrowdVolWindow"]), + "CrowdStart": _runtime_param("CrowdStart", PARAM_DEFAULTS["CrowdStart"]), + "CrowdEnd": _runtime_param("CrowdEnd", PARAM_DEFAULTS["CrowdEnd"]), + "MinCrowdPenalty": _runtime_param("MinCrowdPenalty", PARAM_DEFAULTS["MinCrowdPenalty"]), + "CrowdStart_by_etf": _runtime_list_param("CrowdStart_by_etf"), + "CrowdEnd_by_etf": _runtime_list_param("CrowdEnd_by_etf"), + "MinCrowdPenalty_by_etf": _runtime_list_param("MinCrowdPenalty_by_etf"), + "CrowdRetShort_by_etf": _runtime_list_param("CrowdRetShort_by_etf"), + "CrowdRetMid_by_etf": _runtime_list_param("CrowdRetMid_by_etf"), + "PortfolioVolWindow": _runtime_param("PortfolioVolWindow", PARAM_DEFAULTS["PortfolioVolWindow"]), + "TargetVol": _runtime_param("TargetVol", PARAM_DEFAULTS["TargetVol"]), + "MaxPortfolioVolScale": _runtime_param("MaxPortfolioVolScale", PARAM_DEFAULTS["MaxPortfolioVolScale"]), + "PortfolioVolReliefMode": _runtime_param( + "PortfolioVolReliefMode", + PORTFOLIO_VOL_RELIEF_DEFAULTS["PortfolioVolReliefMode"], + ), + "GoldVolReliefFraction": _runtime_param( + "GoldVolReliefFraction", + PORTFOLIO_VOL_RELIEF_DEFAULTS["GoldVolReliefFraction"], + ), + "GoldVolReliefMaxRatio": _runtime_param( + "GoldVolReliefMaxRatio", + PORTFOLIO_VOL_RELIEF_DEFAULTS["GoldVolReliefMaxRatio"], + ), + "DynamicVolReliefFraction": _runtime_param( + "DynamicVolReliefFraction", + PORTFOLIO_VOL_RELIEF_DEFAULTS["DynamicVolReliefFraction"], + ), + "DynamicVolReliefMaxRatio": _runtime_param( + "DynamicVolReliefMaxRatio", + PORTFOLIO_VOL_RELIEF_DEFAULTS["DynamicVolReliefMaxRatio"], + ), + "DynamicVolReliefMomentumWindow": _runtime_param( + "DynamicVolReliefMomentumWindow", + PORTFOLIO_VOL_RELIEF_DEFAULTS["DynamicVolReliefMomentumWindow"], + ), + "DynamicVolReliefCovWindow": _runtime_param( + "DynamicVolReliefCovWindow", + PORTFOLIO_VOL_RELIEF_DEFAULTS["DynamicVolReliefCovWindow"], + ), + "MaxWeight": _runtime_param("MaxWeight", PARAM_DEFAULTS["MaxWeight"]), + "MinWeight": _runtime_param("MinWeight", PARAM_DEFAULTS["MinWeight"]), + "RebalanceThreshold": _runtime_param("RebalanceThreshold", PARAM_DEFAULTS["RebalanceThreshold"]), + "MaxTotalWeight": _runtime_param("MaxTotalWeight", PARAM_DEFAULTS["MaxTotalWeight"]), + "ExecutionTimingMode": _runtime_param( + "ExecutionTimingMode", + DEFAULT_EXECUTION_TIMING_MODE, + ), + "use_real_price": _runtime_param("use_real_price", PARAM_DEFAULTS["use_real_price"]), + "fq_mode": _runtime_param("fq_mode", PARAM_DEFAULTS["fq_mode"]), + "history_buffer": _runtime_param("history_buffer", PARAM_DEFAULTS["history_buffer"]), + "audit_token": getattr(g, "audit_token", ""), + "audit_path": getattr(g, "audit_path", ""), + } + + +# ============================================================ +# validate_params — 参数校验 +# ============================================================ +def validate_params(params): + """ + 校验参数合法性,不合法时抛出 ValueError。 + + 校验规则来自技术实现方案 4.1 节参数校验表。 + """ + errors = [] + + if params["MA_long"] <= 0: + errors.append("MA_long must be positive") + ma_long_by_etf = params.get("MA_long_by_etf") + if ma_long_by_etf is not None: + if not isinstance(ma_long_by_etf, (list, tuple)): + errors.append("MA_long_by_etf must be a list or tuple when provided") + else: + if len(ma_long_by_etf) != len(params["etf_pool"]): + errors.append("MA_long_by_etf length must match etf_pool") + for window in ma_long_by_etf: + if not isinstance(window, (int, float)) or window <= 0: + errors.append("MA_long_by_etf values must be positive") + break + if abs(params["w20"] + params["w60"] + params["w120"] - 1.0) > 1e-8: + errors.append("momentum weights must sum to 1") + if params["TopK"] < 1: + errors.append("TopK must be >= 1") + if not (0 < params["MaxWeight"] <= params["MaxTotalWeight"] <= 1): + errors.append("MaxWeight must be in (0, MaxTotalWeight] and MaxTotalWeight <= 1") + if not (0 <= params["MinWeight"] <= params["MaxWeight"]): + errors.append("MinWeight must be in [0, MaxWeight]") + if params["TargetVol"] <= 0: + errors.append("TargetVol must be positive") + if params["PortfolioVolReliefMode"] not in PORTFOLIO_VOL_RELIEF_MODES: + errors.append("PortfolioVolReliefMode must be one of %s" % (PORTFOLIO_VOL_RELIEF_MODES,)) + for fraction_name in ("GoldVolReliefFraction", "DynamicVolReliefFraction"): + value = params[fraction_name] + if not (0.0 <= value <= 1.0): + errors.append("%s must be in [0.0, 1.0]" % fraction_name) + for ratio_name in ("GoldVolReliefMaxRatio", "DynamicVolReliefMaxRatio"): + value = params[ratio_name] + if value <= 1.0: + errors.append("%s must be > 1.0" % ratio_name) + for window_name in ("DynamicVolReliefMomentumWindow", "DynamicVolReliefCovWindow"): + value = params[window_name] + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + errors.append("%s must be a positive integer" % window_name) + if params["RSRS_M"] <= 0 or params["RSRS_N"] <= 1: + errors.append("RSRS_M must be positive and RSRS_N must be > 1") + if not (0 <= params["CrowdStart"] < params["CrowdEnd"] <= 1): + errors.append("Crowd thresholds must satisfy 0 <= CrowdStart < CrowdEnd <= 1") + for per_etf_name in ("CrowdStart_by_etf", "CrowdEnd_by_etf", "MinCrowdPenalty_by_etf", + "CrowdRetShort_by_etf", "CrowdRetMid_by_etf"): + per_etf_val = params.get(per_etf_name) + if per_etf_val is not None: + if not isinstance(per_etf_val, (list, tuple)): + errors.append("%s must be a list or tuple when provided" % per_etf_name) + elif len(per_etf_val) != len(params["etf_pool"]): + errors.append("%s length must match etf_pool" % per_etf_name) + starts_etf = params.get("CrowdStart_by_etf") + ends_etf = params.get("CrowdEnd_by_etf") + if starts_etf is not None and ends_etf is not None: + for idx, (s, e) in enumerate(zip(starts_etf, ends_etf)): + if not (0 <= s < e <= 1): + errors.append("CrowdStart_by_etf[%d]=%s must satisfy 0 <= start < end <= 1, got end=%s" % (idx, s, e)) + break + elif starts_etf is not None: + for idx, s in enumerate(starts_etf): + if not (0 <= s < params["CrowdEnd"] <= 1): + errors.append("CrowdStart_by_etf[%d]=%s must satisfy 0 <= start < CrowdEnd=%s" % (idx, s, params["CrowdEnd"])) + break + elif ends_etf is not None: + for idx, e in enumerate(ends_etf): + if not (params["CrowdStart"] < e <= 1): + errors.append("CrowdEnd_by_etf[%d]=%s must satisfy CrowdStart=%s < end <= 1" % (idx, e, params["CrowdStart"])) + break + if params["MomentumTiltStrength"] < 0: + errors.append("MomentumTiltStrength must be >= 0") + if not (0 < params["MomentumTiltMin"] <= 1 <= params["MomentumTiltMax"]): + errors.append("Momentum tilt bounds must satisfy 0 < min <= 1 <= max") + extreme_start = params["MomentumExtremeScoreStart"] + if extreme_start is not None and not (0 < extreme_start <= 1): + errors.append("MomentumExtremeScoreStart must be None or satisfy 0 < start <= 1") + if not (1 <= params["MomentumExtremeTiltCap"] <= params["MomentumTiltMax"]): + errors.append("MomentumExtremeTiltCap must satisfy 1 <= cap <= MomentumTiltMax") + if not (0 < params["RSRSTiltMin"] <= 1 <= params["RSRSTiltMax"]): + errors.append("RSRS tilt bounds must satisfy 0 < min <= 1 <= max") + if params["ExecutionTimingMode"] not in EXECUTION_TIMING_MODES: + errors.append("ExecutionTimingMode must be one of %s" % (EXECUTION_TIMING_MODES,)) + if len(params["etf_pool"]) != len(params["etf_names"]): + errors.append("etf_pool and etf_names must have the same length") + for etf, name in zip(params["etf_pool"], params["etf_names"]): + if str(etf) not in str(name): + errors.append("etf_names must include JoinQuant security code: %s" % etf) + + if errors: + raise ValueError("; ".join(errors)) + + +def resolve_ma_long_windows(params): + """返回与 etf_pool 对齐的趋势均线窗口。""" + ma_long_by_etf = params.get("MA_long_by_etf") + if ma_long_by_etf is None: + return [params["MA_long"]] * len(params["etf_pool"]) + return list(ma_long_by_etf) + + +def resolve_crowd_thresholds(params): + """返回与 etf_pool 对齐的 (CrowdStart, CrowdEnd, MinCrowdPenalty) 三元组列表。""" + n = len(params["etf_pool"]) + starts = params.get("CrowdStart_by_etf") + starts = list(starts) if starts is not None else [params["CrowdStart"]] * n + ends = params.get("CrowdEnd_by_etf") + ends = list(ends) if ends is not None else [params["CrowdEnd"]] * n + mins = params.get("MinCrowdPenalty_by_etf") + mins = list(mins) if mins is not None else [params["MinCrowdPenalty"]] * n + return list(zip(starts, ends, mins)) + + +def resolve_crowd_ret_windows(params): + """返回与 etf_pool 对齐的 (CrowdRetShort, CrowdRetMid) 二元组列表。""" + n = len(params["etf_pool"]) + shorts = params.get("CrowdRetShort_by_etf") + shorts = list(shorts) if shorts is not None else [params["CrowdRetShort"]] * n + mids = params.get("CrowdRetMid_by_etf") + mids = list(mids) if mids is not None else [params["CrowdRetMid"]] * n + return list(zip(shorts, mids)) + + +# ============================================================ +# initialize — 策略初始化 +# ============================================================ +def initialize(context): + """ + 由聚宽框架在回测/模拟启动时自动调用一次。 + + 作用: + - 向 g 对象写入全部策略参数 + - 设置交易费用(场内基金免印花税,佣金万分之一) + - 设置固定滑点 0 + - 注册每周开盘调仓任务 + """ + set_parameter(context) + validate_params(snapshot_params()) + write_file(g.audit_path, "", append=False) + audit_event("run_start", context, params=snapshot_params()) + set_option('use_real_price', g.use_real_price) + set_option("avoid_future_data", True) + + set_order_cost( + OrderCost( + open_tax=0, + close_tax=0, + open_commission=0.0001, + close_commission=0.0001, + min_commission=0 + ), + type='fund' + ) + + set_slippage(FixedSlippage(0.0), type='fund') + + if g.ExecutionTimingMode == "baseline": + run_weekly( + weekly_check, + weekday=1, + time='open', + reference_security='000300.XSHG' + ) + elif g.ExecutionTimingMode == "logic-2-delay-only": + run_weekly( + prepare_delay_only_rebalance, + weekday=1, + time='open', + reference_security='000300.XSHG' + ) + run_daily( + execute_pending_rebalance, + time='open', + reference_security='000300.XSHG' + ) + elif g.ExecutionTimingMode == "logic-3-live-like": + run_weekly( + mark_live_like_signal_day, + weekday=1, + time='open', + reference_security='000300.XSHG' + ) + run_daily( + execute_live_like_rebalance, + time='open', + reference_security='000300.XSHG' + ) + else: + run_weekly( + prepare_weekend_close_rebalance, + weekday=-1, + time='15:30', + force=False, + ) + run_daily( + execute_weekend_close_rebalance, + time='open', + reference_security='000300.XSHG' + ) + + +def on_strategy_end(context): + """Record the final audit marker used by local completeness checks.""" + portfolio = getattr(context, "portfolio", None) + audit_event( + "run_end", + context, + total_value=getattr(portfolio, "total_value", None), + cash=getattr(portfolio, "cash", None), + ) + + +# ============================================================ +# set_parameter — 策略参数集中设置 +# ============================================================ +def set_parameter(context): + """ + 将所有策略参数写入 g 全局对象,便于集中管理和回测参数扫描。 + + 参数类别:资产池、趋势门槛、动量选择、风险平价、RSRS 修正、 + 拥挤度惩罚、组合波动率控制、仓位交易约束。 + """ + + # ---- 资产池 ---- + g.etf_pool = _copy_runtime_default(PARAM_DEFAULTS["etf_pool"]) + g.etf_names = load_etf_display_names(g.etf_pool) + + # ---- 趋势门槛 ---- + g.MA_long = PARAM_DEFAULTS["MA_long"] + g.MA_long_by_etf = _copy_runtime_default(PARAM_DEFAULTS["MA_long_by_etf"]) + + # ---- 动量选择 ---- + g.MomShort = PARAM_DEFAULTS["MomShort"] + g.MomMid = PARAM_DEFAULTS["MomMid"] + g.MomLong = PARAM_DEFAULTS["MomLong"] + g.w20 = PARAM_DEFAULTS["w20"] + g.w60 = PARAM_DEFAULTS["w60"] + g.w120 = PARAM_DEFAULTS["w120"] + g.TopK = PARAM_DEFAULTS["TopK"] + + # ---- 风险平价 ---- + g.VolWindow = PARAM_DEFAULTS["VolWindow"] + g.annual_factor = PARAM_DEFAULTS["annual_factor"] + + # ---- RSRS 修正 ---- + g.RSRS_N = PARAM_DEFAULTS["RSRS_N"] # 回归窗口 + g.RSRS_M = PARAM_DEFAULTS["RSRS_M"] # 标准化窗口 + g.RSRS_NegativeFullCut = PARAM_DEFAULTS["RSRS_NegativeFullCut"] + g.RSRSMinMultiplier = PARAM_DEFAULTS["RSRSMinMultiplier"] + g.RSRSMaxMultiplier = PARAM_DEFAULTS["RSRSMaxMultiplier"] + + # ---- 动量倾斜(资产间相对信号) ---- + g.MomentumTiltStrength = PARAM_DEFAULTS["MomentumTiltStrength"] + g.MomentumTiltMin = PARAM_DEFAULTS["MomentumTiltMin"] + g.MomentumTiltMax = PARAM_DEFAULTS["MomentumTiltMax"] + g.MomentumExtremeScoreStart = PARAM_DEFAULTS["MomentumExtremeScoreStart"] + g.MomentumExtremeTiltCap = PARAM_DEFAULTS["MomentumExtremeTiltCap"] + + # ---- RSRS 倾斜(资产间相对信号) ---- + g.RSRSTiltMin = PARAM_DEFAULTS["RSRSTiltMin"] + g.RSRSTiltMax = PARAM_DEFAULTS["RSRSTiltMax"] + + # ---- 拥挤度惩罚 ---- + g.CrowdWindow = PARAM_DEFAULTS["CrowdWindow"] + g.CrowdRetShort = PARAM_DEFAULTS["CrowdRetShort"] + g.CrowdRetMid = PARAM_DEFAULTS["CrowdRetMid"] + g.AmountMAWindow = PARAM_DEFAULTS["AmountMAWindow"] + g.DeviationMAWindow = PARAM_DEFAULTS["DeviationMAWindow"] + g.CrowdVolWindow = PARAM_DEFAULTS["CrowdVolWindow"] + g.CrowdStart = PARAM_DEFAULTS["CrowdStart"] + g.CrowdEnd = PARAM_DEFAULTS["CrowdEnd"] + g.MinCrowdPenalty = PARAM_DEFAULTS["MinCrowdPenalty"] + g.CrowdStart_by_etf = _copy_runtime_default(PARAM_DEFAULTS["CrowdStart_by_etf"]) + g.CrowdEnd_by_etf = PARAM_DEFAULTS["CrowdEnd_by_etf"] + g.MinCrowdPenalty_by_etf = PARAM_DEFAULTS["MinCrowdPenalty_by_etf"] + g.CrowdRetShort_by_etf = PARAM_DEFAULTS["CrowdRetShort_by_etf"] + g.CrowdRetMid_by_etf = PARAM_DEFAULTS["CrowdRetMid_by_etf"] + + # ---- 组合波动率控制 ---- + g.PortfolioVolWindow = PARAM_DEFAULTS["PortfolioVolWindow"] + g.TargetVol = PARAM_DEFAULTS["TargetVol"] + g.MaxPortfolioVolScale = PARAM_DEFAULTS["MaxPortfolioVolScale"] + g.PortfolioVolReliefMode = PARAM_DEFAULTS["PortfolioVolReliefMode"] + g.GoldVolReliefFraction = PARAM_DEFAULTS["GoldVolReliefFraction"] + g.GoldVolReliefMaxRatio = PARAM_DEFAULTS["GoldVolReliefMaxRatio"] + g.DynamicVolReliefFraction = PARAM_DEFAULTS["DynamicVolReliefFraction"] + g.DynamicVolReliefMaxRatio = PARAM_DEFAULTS["DynamicVolReliefMaxRatio"] + g.DynamicVolReliefMomentumWindow = PARAM_DEFAULTS["DynamicVolReliefMomentumWindow"] + g.DynamicVolReliefCovWindow = PARAM_DEFAULTS["DynamicVolReliefCovWindow"] + + # ---- 仓位与交易约束 ---- + g.MaxWeight = PARAM_DEFAULTS["MaxWeight"] + g.MinWeight = PARAM_DEFAULTS["MinWeight"] + g.RebalanceThreshold = PARAM_DEFAULTS["RebalanceThreshold"] + g.MaxTotalWeight = PARAM_DEFAULTS["MaxTotalWeight"] + g.ExecutionTimingMode = DEFAULT_EXECUTION_TIMING_MODE + + # ---- 数据与基准 ---- + # 复权模式:fq='pre' 在 FQ A/B 对比测试中证实对场内基金会 + # 导致 get_price 返回空数据(2025-04~2026-04 区间复现)。 + # 故默认关闭复权。参考 FQ comparison: R2/backtest_runs/*/report/fq-comparison.md + g.use_real_price = PARAM_DEFAULTS["use_real_price"] + g.fq_mode = PARAM_DEFAULTS["fq_mode"] # 不复权(场内基金默认) + ma_long_max = max(g.MA_long_by_etf) if g.MA_long_by_etf else g.MA_long + g.live_days = max( + ma_long_max, g.MomLong, g.RSRS_M, + g.CrowdWindow, g.PortfolioVolWindow + ) + 50 + g.history_buffer = PARAM_DEFAULTS["history_buffer"] + g.benchmark = PARAM_DEFAULTS["benchmark"] + g.audit_token = JQ_AUTO_AUDIT_TOKEN + g.audit_path = "%s/%s.jsonl" % (JQ_AUTO_AUDIT_DIR, g.audit_token) + g.audit_seq = 0 + g.pending_rebalances = [] + g.pending_live_like_signal_days = [] + + +# ============================================================ +# _log_step — 调仓中间量诊断日志 +# ============================================================ +def _log_step(name, cn_name, pool, values, fmt=".4f", etf_names=None): + """ + 以 "[中文名] name: 基金名(聚宽代码)=value" 格式逐只打印调仓中间量,便于云端回测诊断。 + + 不在本地单测中验证日志格式,只保证聚宽云端 log.info 可输出。 + """ + labels = build_etf_display_names(pool, etf_names) + parts = ["%s=%%s" % label for label in labels] + template = "[%s] %s: " % (cn_name, name) + ", ".join(parts) + formatted = tuple(format(v, fmt) for v in values) + log.info(template, *formatted) + + +# ============================================================ +# compose_raw_weights — 权重合成 +# ============================================================ +def compose_raw_weights(tilted_weights, trend_gates, crowd_penalties): + """ + 合成各模块输出为 RawWeight。 + + 动量与 RSRS 已经体现在 TiltedWeight 中,不再作为独立乘数。 + TrendGate 仍保留作为二次保护。不重新归一化。 + """ + n = len(tilted_weights) + raw = np.zeros(n) + for i in range(n): + raw[i] = ( + tilted_weights[i] + * trend_gates[i] + * crowd_penalties[i] + ) + return raw + + +# ============================================================ +# weekly_check — 周频调仓主函数 +# ============================================================ +def _as_date(value): + """把聚宽日期、pandas 日期或字符串统一成 date。""" + if value is None: + return None + if isinstance(value, datetime): + return value.date() + if isinstance(value, date): + return value + try: + return pd.Timestamp(value).date() + except Exception: + return value + + +def _context_trade_date(context): + """返回当前任务对应的自然日。""" + return _as_date(getattr(context, "current_dt", None)) + + +def _same_iso_week(left, right): + left = _as_date(left) + right = _as_date(right) + if left is None or right is None: + return False + return left.isocalendar()[:2] == right.isocalendar()[:2] + + +def _float_list(values): + try: + if hasattr(values, "tolist"): + values = values.tolist() + except Exception: + pass + return [float(value) for value in list(values or [])] + + +def _weekend_close_batch_id(signal_date): + token = str(getattr(g, "audit_token", "manual")).replace("/", "_").replace("\\", "_") + return "%s-weekend-close-%s" % (token, signal_date) + + +def _enrich_weekend_close_plan(context, plan, signal_date): + weights = _float_list(plan.get("final_weights")) + portfolio = getattr(context, "portfolio", None) + total_value = getattr(portfolio, "total_value", None) + plan["final_weights"] = weights + plan["target_weights"] = weights + plan["target_values"] = [total_value * weight for weight in weights] if total_value is not None else [] + plan["portfolio_total_value"] = total_value + plan["signal_date"] = signal_date + plan["asof_date"] = signal_date + plan["prepared_date"] = signal_date + plan["trade_date"] = plan.get("trade_date") + plan["batch_id"] = plan.get("batch_id") or _weekend_close_batch_id(signal_date) + plan["plan_cached"] = True + plan["signal_notice_sent"] = False + return plan + + +def _report_signal_plan(plan): + if FeishuRelayTools is None: + return False + reporter = getattr(FeishuRelayTools, "report_signal_plan", None) + if not callable(reporter): + return False + try: + return bool(reporter(plan)) + except Exception as exc: + log.warning("feishu signal notice failed: %s", exc) + return False + + +def _suppress_execution_notice(batch_id): + if FeishuRelayTools is None: + return False + suppress = getattr(FeishuRelayTools, "suppress_execution_notice", None) + if not callable(suppress): + return False + try: + suppress(batch_id=batch_id, reason="signal_notice_already_sent") + return True + except Exception as exc: + log.warning("feishu execution notice suppress failed: %s", exc) + return False + + +def _resume_execution_notice(): + if FeishuRelayTools is None: + return + resume = getattr(FeishuRelayTools, "resume_execution_notice", None) + if not callable(resume): + return + try: + resume() + except Exception as exc: + log.warning("feishu execution notice resume failed: %s", exc) + + +def build_rebalance_plan(context, asof_date=None): + """生成一次调仓所需的完整信号快照,但不直接下单。 + + 流程:TrendGate → RPWeight → MomentumScore → MomentumTilt + → RSRSTilt → TiltedWeight → CrowdPenalty + → PortfolioVolScale → FinalWeight + """ + params = snapshot_params() + pool = params["etf_pool"] + etf_names = params["etf_names"] + + # 1. 拉取历史数据 + if asof_date is not None: + asof_date = _as_date(asof_date) + else: + asof_date = getattr(context, "previous_date", None) + prices = get_history_data(context, pool, params, asof_date=asof_date) + + # 2. 计算趋势门槛 + trend_gates = compute_trend_gates(prices, pool, params) + _log_step("TrendGate", "趋势门槛", pool, trend_gates, fmt=".0f", etf_names=etf_names) + + # 3. 风险平价基础权重(所有趋势成立资产参与) + active_mask = [gate > 0 for gate in trend_gates] + rp_weights = compute_rp_weights(prices, pool, active_mask, params) + _log_step("RPWeight", "风险平价权重", pool, rp_weights, fmt=".4f", etf_names=etf_names) + + # 4. 计算动量分数 + momentum_scores = compute_momentum_scores(prices, pool, trend_gates, params) + _log_step("MomentumScore", "动量分数", pool, momentum_scores, fmt=".4f", etf_names=etf_names) + + # 5. 动量倾斜乘数 + momentum_tilts = compute_momentum_tilt_multipliers(momentum_scores, trend_gates, params) + _log_step("MomentumTilt", "动量倾斜乘数", pool, momentum_tilts, fmt=".4f", etf_names=etf_names) + + # 6. RSRS 倾斜乘数 + rsrs_tilts = compute_rsrs_tilt_multipliers(prices, pool, trend_gates, params) + _log_step("RSRSTilt", "RSRS倾斜乘数", pool, rsrs_tilts, fmt=".4f", etf_names=etf_names) + + # 7. 合成倾斜权重(动量 + RSRS 同时参与相对倾斜,活跃资产内重新归一化) + tilted_weights = apply_relative_tilts(rp_weights, trend_gates, momentum_tilts, rsrs_tilts) + _log_step("TiltedWeight", "倾斜合成权重", pool, tilted_weights, fmt=".4f", etf_names=etf_names) + + # 8. 拥挤度线性惩罚乘数 + crowd_penalties = compute_crowd_penalties(prices, pool, params) + _log_step("CrowdPenalty", "拥挤度惩罚", pool, crowd_penalties, fmt=".4f", etf_names=etf_names) + + # 9. 合成 RawWeight(不重新归一化) + raw_weights = compose_raw_weights(tilted_weights, trend_gates, crowd_penalties) + + # 10. 组合波动率缩放 + portfolio_vol_asset_scales, portfolio_vol_meta = compute_portfolio_vol_asset_scales( + prices, pool, raw_weights, params + ) + portfolio_vol_scale = portfolio_vol_meta["base_scale"] + log.info("[组合波动率缩放] PortfolioVolScale=%.4f", portfolio_vol_scale) + + # 11. 最终权重 + final_weights = raw_weights * portfolio_vol_asset_scales + _log_step("FinalWeight", "最终权重", pool, final_weights, fmt=".4f", etf_names=etf_names) + + # 12. 应用交易约束 + final_weights_before_constraints = np.copy(final_weights) + final_weights = apply_weight_constraints(final_weights, params) + audit_event( + "rebalance_signals", + context, + pool=pool, + etf_names=etf_names, + params=params, + trend_gates=trend_gates, + rp_weights=rp_weights, + momentum_scores=momentum_scores, + momentum_tilts=momentum_tilts, + rsrs_tilts=rsrs_tilts, + tilted_weights=tilted_weights, + crowd_penalties=crowd_penalties, + raw_weights=raw_weights, + portfolio_vol_scale=portfolio_vol_scale, + portfolio_vol_relief_mode=portfolio_vol_meta["mode"], + portfolio_vol_ratio=portfolio_vol_meta["vol_ratio"], + portfolio_vol_base_scale=portfolio_vol_meta["base_scale"], + portfolio_vol_asset_scales=portfolio_vol_asset_scales, + portfolio_vol_relief_asset=portfolio_vol_meta["relief_asset"], + portfolio_vol_relief_weight=portfolio_vol_meta["relief_weight"], + portfolio_vol_relief_reason=portfolio_vol_meta["reason"], + final_weights_before_constraints=final_weights_before_constraints, + final_weights=final_weights, + execution_timing_mode=params["ExecutionTimingMode"], + asof_date=asof_date, + trade_date=_context_trade_date(context), + ) + + return { + "pool": pool, + "final_weights": final_weights, + "params": params, + "asof_date": asof_date, + "prepared_date": _context_trade_date(context), + } + + +def weekly_check(context): + """每周开盘时生成信号并立即执行调仓。""" + plan = build_rebalance_plan(context) + execute_rebalance(context, plan["pool"], plan["final_weights"], plan["params"]) + + +def prepare_delay_only_rebalance(context): + """按 baseline 口径先生成信号,延后到下一交易日开盘执行。""" + plan = build_rebalance_plan(context) + g.pending_rebalances.append(plan) + audit_event( + "rebalance_prepared", + context, + execution_timing_mode=plan["params"]["ExecutionTimingMode"], + asof_date=plan["asof_date"], + prepared_date=plan["prepared_date"], + final_weights=plan["final_weights"], + ) + + +def execute_pending_rebalance(context): + """执行 logic-2 中已缓存、且至少延后一交易日的调仓。""" + queue = getattr(g, "pending_rebalances", []) + if not queue: + return + + pending = queue[0] + trade_date = _context_trade_date(context) + prepared_date = pending.get("prepared_date") + if trade_date is None or prepared_date is None or trade_date <= prepared_date: + audit_event( + "pending_rebalance_wait", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + asof_date=pending.get("asof_date"), + prepared_date=prepared_date, + trade_date=trade_date, + ) + return + + audit_event( + "pending_rebalance_execute", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + asof_date=pending.get("asof_date"), + prepared_date=prepared_date, + trade_date=trade_date, + final_weights=pending["final_weights"], + ) + execute_rebalance(context, pending["pool"], pending["final_weights"], pending["params"]) + g.pending_rebalances.pop(0) + + +def mark_live_like_signal_day(context): + """记录本周首个交易日,供下一交易日开盘生成并执行 logic-3 信号。""" + signal_date = _context_trade_date(context) + g.pending_live_like_signal_days.append(signal_date) + audit_event( + "live_like_signal_marked", + context, + execution_timing_mode=snapshot_params()["ExecutionTimingMode"], + signal_date=signal_date, + ) + + +def execute_live_like_rebalance(context): + """在首个交易日之后的下一交易日开盘,生成并执行 logic-3 信号。""" + queue = getattr(g, "pending_live_like_signal_days", []) + if not queue: + return + + signal_date = queue[0] + trade_date = _context_trade_date(context) + if trade_date is None or signal_date is None or trade_date <= signal_date: + audit_event( + "live_like_wait", + context, + execution_timing_mode=snapshot_params()["ExecutionTimingMode"], + signal_date=signal_date, + trade_date=trade_date, + ) + return + + asof_date = getattr(context, "previous_date", None) + if asof_date != signal_date: + audit_event( + "live_like_skip", + context, + execution_timing_mode=snapshot_params()["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=asof_date, + trade_date=trade_date, + reason="previous_date_not_signal_date", + ) + queue.pop(0) + return + + audit_event( + "live_like_rebalance_execute", + context, + execution_timing_mode=snapshot_params()["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=asof_date, + trade_date=trade_date, + ) + weekly_check(context) + queue.pop(0) + + +def prepare_weekend_close_rebalance(context): + """收盘后生成候选信号 plan,次开盘跨周时执行。""" + signal_date = _context_trade_date(context) + params = snapshot_params() + if signal_date is None: + audit_event( + "weekend_close_signal_skip", + context, + execution_timing_mode=params["ExecutionTimingMode"], + signal_date=signal_date, + reason="signal_date_unavailable", + ) + return + + plan = build_rebalance_plan(context, asof_date=signal_date) + plan = _enrich_weekend_close_plan(context, plan, signal_date) + plan["signal_notice_sent"] = _report_signal_plan(plan) + g.pending_rebalances = [plan] + audit_event( + "weekend_close_plan_cached", + context, + execution_timing_mode=plan["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=signal_date, + batch_id=plan["batch_id"], + target_weights=plan["target_weights"], + target_values=plan["target_values"], + signal_notice_sent=plan["signal_notice_sent"], + plan_cached=True, + final_weights=plan["final_weights"], + ) + + +def execute_weekend_close_rebalance(context): + """次交易日开盘执行已缓存的收盘信号 plan,不重新计算信号。""" + queue = getattr(g, "pending_rebalances", []) + if not queue: + return + + pending = queue[0] + trade_date = _context_trade_date(context) + signal_date = pending.get("signal_date") + if trade_date is None or signal_date is None: + audit_event( + "weekend_close_execute_wait", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + trade_date=trade_date, + ) + return + if _same_iso_week(signal_date, trade_date): + audit_event( + "weekend_close_plan_discarded", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + trade_date=trade_date, + reason="not_last_trade_day_of_week", + ) + g.pending_rebalances.pop(0) + return + + pending["trade_date"] = trade_date + suppress_reason = "signal_notice_already_sent" if pending.get("signal_notice_sent") else "" + notice_suppressed = _suppress_execution_notice(pending.get("batch_id")) if suppress_reason else False + audit_event( + "weekend_close_execute", + context, + execution_timing_mode=pending["params"]["ExecutionTimingMode"], + signal_date=signal_date, + asof_date=pending.get("asof_date"), + batch_id=pending.get("batch_id"), + trade_date=trade_date, + execution_order_called=True, + execution_notice_suppressed=notice_suppressed, + suppress_reason=suppress_reason if notice_suppressed else "", + final_weights=pending["final_weights"], + ) + try: + execute_rebalance(context, pending["pool"], pending["final_weights"], pending["params"]) + finally: + if notice_suppressed: + _resume_execution_notice() + g.pending_rebalances.pop(0) + + +# ============================================================ +# normalize_field_frame — 数据返回结构归一化 +# ============================================================ +def normalize_field_frame(raw, field, pool): + """ + 将 get_price 返回的单 ETF 结果归一化,辅助测试与本地诊断。 + + 处理规则: + - None 或空 DataFrame → 返回 columns=pool 的空 DataFrame + - 普通 DataFrame → reindex(columns=pool) 补全缺列 + """ + if raw is None: + return pd.DataFrame(columns=pool) + if not isinstance(raw, pd.DataFrame): + return pd.DataFrame(columns=pool) + if len(raw) == 0: + return pd.DataFrame(columns=pool) + + raw = raw.reindex(columns=pool) + return raw.dropna(how='all') + + +# ============================================================ +# compute_history_count — 计算所需历史数据长度 +# ============================================================ +def compute_history_count(params): + """ + 按模块显式计算所需历史数据长度。 + + 说明: + - 动量和收益率类计算需要多取 1 日 + - RSRS 至少需要 RSRS_M + RSRS_N - 1 + - buffer 用于容忍停牌、缺失值、上市初期数据不足 + """ + requirements = [ + max(resolve_ma_long_windows(params)), + max(params["MomShort"], params["MomMid"], params["MomLong"]) + 1, + params["VolWindow"] + 1, + params["RSRS_M"] + params["RSRS_N"] - 1, + params["CrowdWindow"], + params["PortfolioVolWindow"] + 1, + ] + return max(requirements) + params.get("history_buffer", 50) + + +# ============================================================ +# fetch_field + get_history_data — 拉取历史行情数据 +# ============================================================ +def fetch_field(pool, field, count, params, end_date=None): + """ + 逐 ETF 拉取单字段数据,返回 DataFrame(index=日期, columns=ETF代码)。 + + 单标的 + panel=False 在聚宽云端稳定返回 DataFrame(columns=字段名), + 多标的传入 panel=False 可能仍返回 Panel,因此改为逐只拉取后手工组装。 + + end_date: 历史数据截止日期。开盘调仓时需传入 context.previous_date + 以避免当日收盘价未来数据。 + """ + series_map = {} + for etf in pool: + df = get_price( + etf, + count=count, + end_date=end_date, + frequency='daily', + fields=[field], + skip_paused=True, + fq=params["fq_mode"], + panel=False, + ) + if df is not None and len(df) > 0: + series_map[etf] = df[field] + + if not series_map: + return pd.DataFrame(columns=pool) + + result = pd.DataFrame(series_map) + result = result.reindex(columns=pool) + return result.dropna(how='all') + + +def get_history_data(context, pool, params, asof_date=None): + """ + 拉取足够长的历史 OHLC + 成交额数据,并预计算日收益率。 + + 返回 dict: + close/high/low/amount: DataFrame(index=日期, columns=ETF代码) + close_ret: DataFrame(index=日期, columns=ETF代码),close 的 pct_change() + """ + needed = compute_history_count(params) + history_end_date = asof_date if asof_date is not None else getattr(context, "previous_date", None) + + prices = {} + prices['close'] = fetch_field(pool, 'close', needed, params, end_date=history_end_date) + prices['high'] = fetch_field(pool, 'high', needed, params, end_date=history_end_date) + prices['low'] = fetch_field(pool, 'low', needed, params, end_date=history_end_date) + prices['amount'] = fetch_field(pool, 'money', needed, params, end_date=history_end_date) + + # 预计算日收益率,下游风险平价和组合波动率复用同一份 + prices['close_ret'] = prices['close'].pct_change() + + # 数据新鲜度日志:确保历史数据不晚于 context.previous_date + close_df = prices['close'] + last_dt = close_df.index[-1] if len(close_df) else None + log.info("history end_date=%s, asof_date=%s", last_dt, history_end_date) + + return prices + + +# ============================================================ +# compute_trend_gates — 趋势门槛(硬过滤) +# ============================================================ +def compute_trend_gates(prices, pool, params): + """ + 使用趋势均线判断方向,支持按 ETF 分别设置窗口。 + + 返回: list[float],1.0 表示通过,0.0 表示剔除 + """ + close = prices['close'] + ma_windows = resolve_ma_long_windows(params) + + gates = np.zeros(len(pool)) + for i, etf in enumerate(pool): + if etf not in close.columns: + continue + ma_window = ma_windows[i] + series = close[etf].dropna() + if len(series) < ma_window: + continue + ma = series.iloc[-ma_window:].mean() + current_close = series.iloc[-1] + if current_close > ma: + gates[i] = 1.0 + return gates + + +# ============================================================ +# compute_momentum_scores — 多周期排名动量分数 +# ============================================================ +def compute_momentum_scores(prices, pool, trend_gates, params): + """ + 在趋势成立的资产中,计算多周期排名动量分数。 + + 对 close DataFrame 批量取各周期终点收益,再用 rank(pct=True) + 在截面上生成 0~1 排名分数,最后按权重线性组合。 + + 返回: np.array,未通过趋势门槛的资产分数为 0 + """ + close = prices['close'] + n = len(pool) + scores = np.zeros(n) + + windows = [params["MomShort"], params["MomMid"], params["MomLong"]] + period_weights = [params["w20"], params["w60"], params["w120"]] + + active_indices = [i for i in range(n) if trend_gates[i] > 0] + if not active_indices: + return scores + + active_pool = [pool[i] for i in active_indices if pool[i] in close.columns] + if not active_pool: + return scores + active_close = close[active_pool] + if len(active_close) <= max(windows): + return scores + + for j, w in enumerate(windows): + if len(active_close) <= w: + continue + latest = active_close.iloc[-1] + past = active_close.iloc[-(w + 1)] + period_ret = latest / past - 1 # pd.Series, index=ETF代码 + + # rank(pct=True) 将收益率映射到 [0, 1],收益率越高排名越接近 1 + ranks = period_ret.rank(pct=True).fillna(0.0) + + for idx_pos, active_i in enumerate(active_indices): + etf_code = pool[active_i] + scores[active_i] += period_weights[j] * float(ranks.get(etf_code, 0.0)) + + return scores + + +# ============================================================ +# select_topk — TopK 入选 +# ============================================================ +def select_topk(momentum_scores, trend_gates, params): + """ + 在趋势成立资产中按动量分数从高到低选择前 TopK 只。 + + 返回: list[bool],入选为 True + """ + n = len(momentum_scores) + selected = [False] * n + + active = [(i, momentum_scores[i]) for i in range(n) if trend_gates[i] > 0] + active.sort(key=lambda x: x[1], reverse=True) + + k = min(params["TopK"], len(active)) + for idx, _ in active[:k]: + selected[idx] = True + + return selected + + +# ============================================================ +# compute_rp_weights — 逆波动率风险平价 +# ============================================================ +def compute_rp_weights(prices, pool, active_mask, params): + """ + 对活跃资产计算逆波动率风险平价权重。 + + 所有趋势成立资产均参与基础权重计算,不再受 TopK 限制。 + 使用 get_history_data 预计算的 close_ret,避免重复 pct_change()。 + + 返回: np.array + """ + close_ret = prices['close_ret'] + n = len(pool) + vol_window = params["VolWindow"] + annual_factor = params["annual_factor"] + + weights = np.zeros(n) + active_indices = [i for i in range(n) if active_mask[i]] + + if not active_indices: + return weights + + vols = np.zeros(n) + for i in active_indices: + etf = pool[i] + if etf not in close_ret.columns: + continue + daily_ret = close_ret[etf].dropna().iloc[-vol_window:] + if len(daily_ret) < 5: + vols[i] = 1.0 + else: + vols[i] = daily_ret.std() * np.sqrt(annual_factor) + if vols[i] < 1e-8: + vols[i] = 1e-8 + + inverse_vols = np.zeros(n) + for i in active_indices: + if vols[i] > 0: + inverse_vols[i] = 1.0 / vols[i] + + total_inv_vol = inverse_vols.sum() + if total_inv_vol > 0: + weights = inverse_vols / total_inv_vol + + return weights + + +# ============================================================ +# compute_rsrs_multipliers — RSRS 线性修正乘数 +# ============================================================ +def compute_rsrs_multipliers(prices, pool, params): + """ + 对每只 ETF 计算 RSRS 截断线性乘数(旧接口,保留兼容)。 + + 委托 compute_rsrs_adjusted_scores 获取原始信号, + 再按旧公式 clip(1 + RSRS_Adj / NegativeFullCut, Min, Max)。 + + 只减仓,不加仓。 + """ + rsrs_adj = compute_rsrs_adjusted_scores(prices, pool, params) + full_cut = params["RSRS_NegativeFullCut"] + n = len(pool) + + multipliers = np.ones(n) + for i in range(n): + raw_mult = 1.0 + rsrs_adj[i] / full_cut + multipliers[i] = np.clip(raw_mult, params["RSRSMinMultiplier"], params["RSRSMaxMultiplier"]) + + return multipliers + + +# ============================================================ +# compute_rsrs_adjusted_scores — RSRS 原始结构信号 +# ============================================================ +def compute_rsrs_adjusted_scores(prices, pool, params): + """ + 计算每只 ETF 的 RSRS 原始调整信号:RSRSAdj_i = RSRS_Z_i × R2_i。 + + 返回: np.array,数据不足时对应位置为 0。 + """ + high = prices['high'] + low = prices['low'] + n = len(pool) + + N = params["RSRS_N"] + M = params["RSRS_M"] + + scores = np.zeros(n) + + for i, etf in enumerate(pool): + if etf not in high.columns or etf not in low.columns: + continue + h = high[etf].dropna() + l = low[etf].dropna() + + common_idx = h.index.intersection(l.index) + h = h.loc[common_idx] + l = l.loc[common_idx] + + min_len = M + N - 1 + if len(h) < min_len: + continue + + h_roll = h.rolling(N) + l_roll = l.rolling(N) + cov_vals = h_roll.cov(l).dropna().values + var_l_vals = l_roll.var().dropna().values + var_h_vals = h_roll.var().dropna().values + + betas = cov_vals / var_l_vals + r2s = cov_vals ** 2 / (var_h_vals * var_l_vals) + + bad = ( + (var_l_vals < 1e-10) | (var_h_vals < 1e-10) + | (~np.isfinite(betas)) | (~np.isfinite(r2s)) + ) + betas[bad] = 1.0 + r2s[bad] = 0.0 + + if len(betas) < M: + continue + + beta_series = betas[-M:] + mean_beta = np.mean(beta_series) + std_beta = np.std(beta_series) + + if std_beta < 1e-10: + rsrs_z = 0.0 + else: + rsrs_z = (beta_series[-1] - mean_beta) / std_beta + + latest_r2 = r2s[-1] + scores[i] = rsrs_z * latest_r2 + + return scores + + +# ============================================================ +# compute_momentum_tilt_multipliers — 动量相对倾斜乘数 +# ============================================================ +def compute_momentum_tilt_multipliers(momentum_scores, trend_gates, params): + """ + 将动量分数转换为资产间相对倾斜乘数。 + + 活跃资产围绕均值上下倾斜,非活跃资产返回 0。 + 倾斜公式:clip(1 + strength × (score_i - mean_active), min, max) + 若启用极端高动量弱化,则 score_i 达到阈值后再将高位倾斜压到 cap。 + """ + n = len(momentum_scores) + tilts = np.zeros(n) + + active_indices = [i for i in range(n) if trend_gates[i] > 0] + if not active_indices: + return tilts + + active_scores = momentum_scores[active_indices] + mean_score = np.mean(active_scores) + + strength = params["MomentumTiltStrength"] + tilt_min = params["MomentumTiltMin"] + tilt_max = params["MomentumTiltMax"] + extreme_start = params["MomentumExtremeScoreStart"] + extreme_cap = params["MomentumExtremeTiltCap"] + + for i in active_indices: + edge = momentum_scores[i] - mean_score + raw_tilt = 1.0 + strength * edge + tilt = np.clip(raw_tilt, tilt_min, tilt_max) + if extreme_start is not None and momentum_scores[i] >= extreme_start: + tilt = min(tilt, extreme_cap) + tilts[i] = tilt + + return tilts + + +# ============================================================ +# compute_rsrs_tilt_multipliers — RSRS 相对倾斜乘数 +# ============================================================ +def compute_rsrs_tilt_multipliers(prices, pool, trend_gates, params): + """ + 计算 RSRS 原始结构信号,并转换为资产间相对倾斜乘数。 + + 活跃资产围绕均值上下倾斜,非活跃资产返回 0。 + 倾斜公式:clip(1 + (RSRSAdj_i - mean_active) / NegativeFullCut, min, max) + """ + rsrs_adj = compute_rsrs_adjusted_scores(prices, pool, params) + n = len(pool) + tilts = np.zeros(n) + + active_indices = [i for i in range(n) if trend_gates[i] > 0] + if not active_indices: + return tilts + + active_adj = rsrs_adj[active_indices] + mean_adj = np.mean(active_adj) + + full_cut = params["RSRS_NegativeFullCut"] + tilt_min = params["RSRSTiltMin"] + tilt_max = params["RSRSTiltMax"] + + for i in active_indices: + edge = rsrs_adj[i] - mean_adj + raw_tilt = 1.0 + edge / full_cut + tilts[i] = np.clip(raw_tilt, tilt_min, tilt_max) + + return tilts + + +# ============================================================ +# apply_relative_tilts — 倾斜权重合成与归一化 +# ============================================================ +def apply_relative_tilts(rp_weights, trend_gates, momentum_tilts, rsrs_tilts): + """ + 将风险平价基础权重、动量倾斜和 RSRS 倾斜合成,并在活跃资产内归一化。 + + tilted_raw_i = RPWeight_i × MomentumTilt_i × RSRSTilt_i + 归一化到 sum(RPWeight_active),非活跃资产权重为 0。 + """ + n = len(rp_weights) + tilted = np.zeros(n) + + active_indices = [i for i in range(n) if trend_gates[i] > 0 and rp_weights[i] > 0] + if not active_indices: + return tilted + + tilted_raw = np.zeros(n) + for i in active_indices: + tilted_raw[i] = rp_weights[i] * momentum_tilts[i] * rsrs_tilts[i] + + total_raw = tilted_raw[active_indices].sum() + base_total = rp_weights[active_indices].sum() + + if total_raw <= 0 or base_total <= 0: + return np.copy(rp_weights) + + for i in active_indices: + tilted[i] = tilted_raw[i] / total_raw * base_total + + return tilted + + +# ============================================================ +# compute_crowd_penalties — 拥挤度线性惩罚乘数 +# ============================================================ +def compute_crowd_penalties(prices, pool, params): + """ + 对每只 ETF 计算拥挤度线性惩罚乘数。 + + 先在 DataFrame 级批量计算五类指标(ret20/ret60/amt_ma20/deviation/vol20), + 再逐 ETF 取最后一行做分位排名,减少 Python 层逐列 rolling/pct_change 开销。 + + 只减仓,不加仓。 + + 返回: np.array + """ + close = prices['close'] + amount = prices['amount'] + n = len(pool) + + crowd_window = params["CrowdWindow"] + thresholds = resolve_crowd_thresholds(params) # [(start, end, min_penalty), ...] + ret_windows = resolve_crowd_ret_windows(params) # [(short, mid), ...] + + penalties = np.ones(n) + + # 过滤数据不足的 ETF + eligible_etfs = [] + for i, etf in enumerate(pool): + if etf in close.columns and len(close[etf].dropna()) >= crowd_window: + eligible_etfs.append(etf) + else: + penalties[i] = 1.0 + + if not eligible_etfs: + return penalties + + # ---- DataFrame 级批量计算:一次算完所有 ETF 的指标 ---- + close_recent = close[eligible_etfs].iloc[-crowd_window:] + + # 1-2) 短/中期涨幅(按 per-ETF 窗口预计算,避免重复 shift) + ret_short_map = {} + ret_mid_map = {} + for i, etf in enumerate(pool): + if etf not in eligible_etfs: + continue + short_w, mid_w = ret_windows[i] + if short_w not in ret_short_map: + ret_short_map[short_w] = close_recent / close_recent.shift(short_w) - 1 + if mid_w not in ret_mid_map: + ret_mid_map[mid_w] = close_recent / close_recent.shift(mid_w) - 1 + + # 3) 成交额 MA20(仅对有 amount 数据的 ETF) + eligible_amount_cols = [e for e in eligible_etfs if e in amount.columns] + amt_ma20_df = None + if eligible_amount_cols: + amt_aligned = amount[eligible_amount_cols].loc[ + amount.index.intersection(close_recent.index) + ] + if len(amt_aligned) >= params["AmountMAWindow"]: + amt_ma20_df = amt_aligned.rolling(params["AmountMAWindow"]).mean() + + # 4) 偏离均线程度 + ma20_df = close_recent.rolling(params["DeviationMAWindow"]).mean() + deviation_df = close_recent / ma20_df - 1 + + # 5) 短期波动率 + vol20_df = close_recent.pct_change().rolling(params["CrowdVolWindow"]).std() * np.sqrt(params["annual_factor"]) + + # ---- 逐 ETF 从预计算 DataFrame 中取列做分位排名 ---- + for i, etf in enumerate(pool): + if etf not in eligible_etfs: + continue + + indicators = [] + + # ret short (per-ETF window) + short_w = ret_windows[i][0] + col_short = ret_short_map[short_w][etf].dropna() + indicators.append(percentile_rank(col_short.iloc[-1], col_short) if len(col_short) > 1 else 0.5) + + # ret mid (per-ETF window) + mid_w = ret_windows[i][1] + col_mid = ret_mid_map[mid_w][etf].dropna() + indicators.append(percentile_rank(col_mid.iloc[-1], col_mid) if len(col_mid) > 1 else 0.5) + + # amt_ma20 + if amt_ma20_df is not None and etf in amt_ma20_df.columns: + col_amt = amt_ma20_df[etf].dropna() + indicators.append(percentile_rank(col_amt.iloc[-1], col_amt) if len(col_amt) > 1 else 0.5) + else: + indicators.append(0.5) + + # deviation + col_dev = deviation_df[etf].dropna() + indicators.append(percentile_rank(col_dev.iloc[-1], col_dev) if len(col_dev) > 1 else 0.5) + + # vol20 + col_vol = vol20_df[etf].dropna() + indicators.append(percentile_rank(col_vol.iloc[-1], col_vol) if len(col_vol) > 1 else 0.5) + + crowd_score = np.mean(indicators) + + etf_start, etf_end, etf_min = thresholds[i] + if crowd_score <= etf_start: + penalty = 1.0 + elif crowd_score >= etf_end: + penalty = etf_min + else: + penalty = 1.0 - (crowd_score - etf_start) / (etf_end - etf_start) * (1.0 - etf_min) + penalty = max(etf_min, min(1.0, penalty)) + + penalties[i] = penalty + + return penalties + + +# ============================================================ +# percentile_rank — 计算分位数排名(0~1) +# ============================================================ +def percentile_rank(value, series): + """ + 计算 value 在 series 中的分位数(0~1)。 + + 返回 0 表示 value 是序列中最小值,返回 1 表示最大值。 + """ + if len(series) == 0: + return 0.5 + ranked = (series < value).mean() + return float(ranked) + + +# ============================================================ +# compute_portfolio_vol_scale — 组合波动率缩放系数 +# ============================================================ +def compute_portfolio_vol_scale_detail(prices, pool, raw_weights, params): + """ + 根据 RawWeight 和协方差矩阵计算组合波动率,按目标波动率缩放。 + + 使用 get_history_data 预计算的 close_ret,避免重复 pct_change()。 + 只缩不放(最大系数为 1.0)。 + + 返回: (scale, vol_ratio) + """ + close_ret = prices['close_ret'] + vol_window = params["PortfolioVolWindow"] + target_vol = params["TargetVol"] + annual_factor = params["annual_factor"] + + n = len(pool) + active_indices = [i for i in range(n) if raw_weights[i] > 1e-8] + + if not active_indices: + return 1.0, None + + returns_list = [] + for i in active_indices: + etf = pool[i] + if etf not in close_ret.columns: + return 1.0, None + ret = close_ret[etf].dropna().iloc[-vol_window:] + if len(ret) < vol_window: + return 1.0, None + returns_list.append(ret.values) + + if not returns_list: + return 1.0, None + + ret_matrix = np.column_stack(returns_list) + cov_daily = np.atleast_2d(np.cov(ret_matrix, rowvar=False)) + cov_annual = cov_daily * annual_factor + + active_weights = np.array([raw_weights[i] for i in active_indices]) + portfolio_var = active_weights @ cov_annual @ active_weights + portfolio_vol = np.sqrt(max(portfolio_var, 0)) + vol_ratio = float(portfolio_vol / target_vol) + + if portfolio_vol <= target_vol or portfolio_vol < 1e-8: + return 1.0, vol_ratio + + scale = target_vol / portfolio_vol + return min(scale, params["MaxPortfolioVolScale"]), vol_ratio + + +def compute_portfolio_vol_scale(prices, pool, raw_weights, params): + """返回原有组合级波动率缩放标量。""" + scale, _vol_ratio = compute_portfolio_vol_scale_detail(prices, pool, raw_weights, params) + return scale + + +def compute_portfolio_vol_asset_scales(prices, pool, raw_weights, params): + """返回每只 ETF 的组合波控缩放系数和审计元数据。""" + base_scale, vol_ratio = compute_portfolio_vol_scale_detail(prices, pool, raw_weights, params) + asset_scales = np.full(len(pool), base_scale) + mode = params["PortfolioVolReliefMode"] + meta = { + "mode": mode, + "vol_ratio": vol_ratio, + "base_scale": base_scale, + "relief_asset": None, + "relief_weight": 0.0, + "reason": "baseline", + } + if mode == "fixed_gold": + return apply_fixed_gold_vol_relief(pool, raw_weights, asset_scales, meta, params) + if mode == "dyn_marginal": + return apply_dynamic_marginal_vol_relief(prices, pool, raw_weights, asset_scales, meta, params) + return asset_scales, meta + + +def apply_fixed_gold_vol_relief(pool, raw_weights, asset_scales, meta, params): + """固定黄金弱缩放:按参数恢复黄金被组合波控压掉的部分仓位。""" + gold_code = "518880.XSHG" + vol_ratio = meta["vol_ratio"] + base_scale = meta["base_scale"] + + if vol_ratio is None or vol_ratio <= 1.0: + meta["reason"] = "vol_not_above_target" + return asset_scales, meta + if vol_ratio > params["GoldVolReliefMaxRatio"]: + meta["reason"] = "ratio_too_high" + return asset_scales, meta + if gold_code not in pool: + meta["reason"] = "gold_not_in_pool" + return asset_scales, meta + + gold_idx = pool.index(gold_code) + if raw_weights[gold_idx] <= 1e-8: + meta["reason"] = "gold_not_active" + return asset_scales, meta + + new_scale = min(1.0, base_scale + (1.0 - base_scale) * params["GoldVolReliefFraction"]) + asset_scales[gold_idx] = new_scale + meta["relief_asset"] = gold_code + meta["relief_weight"] = float(raw_weights[gold_idx] * (new_scale - base_scale)) + meta["reason"] = "fixed_gold" + return asset_scales, meta + + +def apply_dynamic_marginal_vol_relief(prices, pool, raw_weights, asset_scales, meta, params): + """动态弱缩放:在正动量持仓中选择边际风险最低资产恢复仓位。""" + vol_ratio = meta["vol_ratio"] + base_scale = meta["base_scale"] + + if vol_ratio is None or vol_ratio <= 1.0: + meta["reason"] = "vol_not_above_target" + return asset_scales, meta + if vol_ratio > params["DynamicVolReliefMaxRatio"]: + meta["reason"] = "ratio_too_high" + return asset_scales, meta + + selected_idx, reason = select_dynamic_marginal_relief_asset( + prices, pool, raw_weights, params + ) + if selected_idx is None: + meta["reason"] = reason + return asset_scales, meta + + new_scale = min(1.0, base_scale + (1.0 - base_scale) * params["DynamicVolReliefFraction"]) + asset_scales[selected_idx] = new_scale + meta["relief_asset"] = pool[selected_idx] + meta["relief_weight"] = float(raw_weights[selected_idx] * (new_scale - base_scale)) + meta["reason"] = "selected_low_marginal_risk" + return asset_scales, meta + + +def select_dynamic_marginal_relief_asset(prices, pool, raw_weights, params): + """返回动态弱缩放资产下标和原因。""" + active_indices = [i for i, weight in enumerate(raw_weights) if weight > 1e-8] + if not active_indices: + return None, "no_active_asset" + + close_ret = prices.get("close_ret") + if close_ret is None: + return None, "insufficient_cov_data" + + active_codes = [pool[i] for i in active_indices] + for code in active_codes: + if code not in close_ret.columns: + return None, "insufficient_cov_data" + + cov_window = params["DynamicVolReliefCovWindow"] + active_returns = close_ret[active_codes].dropna().iloc[-cov_window:] + if len(active_returns) < cov_window: + return None, "insufficient_cov_data" + + momentum_window = params["DynamicVolReliefMomentumWindow"] + positive_candidates = [] + for active_pos, code in enumerate(active_codes): + momentum_ret = close_ret[code].dropna().iloc[-momentum_window:] + if len(momentum_ret) < momentum_window: + continue + momentum = float(np.prod(1.0 + momentum_ret.values) - 1.0) + if momentum >= 0.0: + positive_candidates.append(active_pos) + + if not positive_candidates: + return None, "no_positive_momentum_asset" + + cov_daily = np.atleast_2d(np.cov(active_returns.values, rowvar=False)) + cov_annual = cov_daily * params["annual_factor"] + active_weights = np.array([raw_weights[i] for i in active_indices]) + marginal_scores = cov_annual @ active_weights + best_active_pos = min( + positive_candidates, + key=lambda active_pos: marginal_scores[active_pos], + ) + return active_indices[best_active_pos], "selected_low_marginal_risk" + + +# ============================================================ +# apply_weight_constraints — 应用仓位约束 +# ============================================================ +def apply_weight_constraints(final_weights, params): + """ + 应用单资产最大仓位、最小有效仓位和总仓位上限约束。 + + 总仓位约束在单资产约束之后应用,缩放后不重新归一化。 + """ + max_w = params["MaxWeight"] + min_w = params["MinWeight"] + max_total = params["MaxTotalWeight"] + n = len(final_weights) + + result = np.copy(final_weights) + + for i in range(n): + if result[i] > max_w: + result[i] = max_w + if result[i] < min_w: + result[i] = 0.0 + + total = result.sum() + if total > max_total: + result = result * max_total / total + + return result + + +# ============================================================ +# execute_rebalance — 执行调仓 +# ============================================================ +def execute_rebalance(context, pool, final_weights, params): + """ + 根据最终目标权重执行调仓,应用最小调仓阈值。 + 剩余仓位保留为现金。 + + 执行前检查停牌状态,执行后记录订单结果,便于审计和故障定位。 + """ + account_value = context.portfolio.total_value + current_data = get_current_data() + etf_names = build_etf_display_names(pool, params.get("etf_names")) + + for i, etf in enumerate(pool): + etf_name = etf_names[i] + target_value = account_value * final_weights[i] + current_pos = context.portfolio.positions[etf] + current_value = current_pos.total_amount * current_pos.price if current_pos.total_amount > 0 else 0 + current_weight = current_value / account_value if account_value > 0 else 0 + + # 如果目标权重为 0 且当前仓位为 0,跳过 + if final_weights[i] == 0 and current_weight == 0: + audit_event( + "rebalance_order", + context, + action="skip_zero_target_zero_position", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + ) + continue + + # 最小调仓阈值 + if abs(final_weights[i] - current_weight) < params["RebalanceThreshold"]: + audit_event( + "rebalance_order", + context, + action="skip_threshold", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + rebalance_threshold=params["RebalanceThreshold"], + ) + continue + + # 停牌检查 + data = current_data[etf] + if data.paused: + log.warning("skip paused ETF: %s security=%s", etf_name, etf) + audit_event( + "rebalance_order", + context, + action="skip_paused", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + ) + continue + + order_obj = order_target_value(etf, target_value) + if order_obj is None: + log.error( + "order failed: %s security=%s target_value=%.2f target_weight=%.4f current_weight=%.4f", + etf_name, etf, target_value, final_weights[i], current_weight + ) + audit_event( + "rebalance_order", + context, + action="order_failed", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + ) + else: + log.info( + "order sent: %s security=%s target_weight=%.4f current_weight=%.4f target_value=%.2f", + etf_name, etf, final_weights[i], current_weight, target_value + ) + audit_event( + "rebalance_order", + context, + action="order_sent", + etf=etf, + etf_name=etf_name, + target_weight=final_weights[i], + current_weight=current_weight, + target_value=target_value, + order=order_obj, + ) diff --git a/strategies/etf_factor_rotation/variants/logic3_code_variant.json b/strategies/etf_factor_rotation/variants/logic3_code_variant.json new file mode 100644 index 00000000..633a8a52 --- /dev/null +++ b/strategies/etf_factor_rotation/variants/logic3_code_variant.json @@ -0,0 +1,29 @@ +{ + "schema_version": 1, + "variant_id": "logic3_code_variant", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "旧实盘近似代码变体:周一标记信号,下一交易日开盘生成并执行。", + "code_source": { + "type": "git", + "path": "strategies/etf_factor_rotation/variants/code/logic3_code_variant.py" + }, + "params_diff": {}, + "research_refs": [], + "backtest_refs": [], + "report_refs": [], + "created_at": "2026-06-30T16:23:28+00:00", + "updated_at": "2026-06-30T16:23:28+00:00", + "owner": "research-platform", + "created_by": "research-platform", + "updated_by": "research-platform", + "lifecycle": "active", + "payload": { + "code_source": { + "type": "git", + "path": "strategies/etf_factor_rotation/variants/code/logic3_code_variant.py" + }, + "owner": "research-platform" + } +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/variants/variants.json b/strategies/etf_factor_rotation/variants/variants.json index b0413729..942e8fdc 100644 --- a/strategies/etf_factor_rotation/variants/variants.json +++ b/strategies/etf_factor_rotation/variants/variants.json @@ -1,4 +1,32 @@ { "schema_version": 1, - "variants": [] -} + "variants": [ + { + "variant_id": "baseline_original", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "原始正式基线代码:本次实验对照组,来源为本分支起点 origin/main 的正式策略脚本。", + "updated_at": "2026-06-30T20:21:17+00:00", + "detail": "baseline_original.json" + }, + { + "variant_id": "logic3_code_variant", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "旧实盘近似代码变体:周一标记信号,下一交易日开盘生成并执行。", + "updated_at": "2026-06-30T16:23:28+00:00", + "detail": "logic3_code_variant.json" + }, + { + "variant_id": "weekend_close_signal_next_open_variant", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "新实盘对齐代码变体:本周最后交易日收盘生成信号,次交易日开盘执行缓存计划。", + "updated_at": "2026-06-30T16:23:28+00:00", + "detail": "weekend_close_signal_next_open_variant.json" + } + ] +} \ No newline at end of file diff --git a/strategies/etf_factor_rotation/variants/weekend_close_signal_next_open_variant.json b/strategies/etf_factor_rotation/variants/weekend_close_signal_next_open_variant.json new file mode 100644 index 00000000..842c1eff --- /dev/null +++ b/strategies/etf_factor_rotation/variants/weekend_close_signal_next_open_variant.json @@ -0,0 +1,29 @@ +{ + "schema_version": 1, + "variant_id": "weekend_close_signal_next_open_variant", + "variant_type": "structural", + "status": "candidate", + "merge_status": "not_merged", + "description": "新实盘对齐代码变体:本周最后交易日收盘生成信号,次交易日开盘执行缓存计划。", + "code_source": { + "type": "git", + "path": "strategies/etf_factor_rotation/variants/code/weekend_close_signal_next_open_variant.py" + }, + "params_diff": {}, + "research_refs": [], + "backtest_refs": [], + "report_refs": [], + "created_at": "2026-06-30T16:23:28+00:00", + "updated_at": "2026-06-30T16:23:28+00:00", + "owner": "research-platform", + "created_by": "research-platform", + "updated_by": "research-platform", + "lifecycle": "active", + "payload": { + "code_source": { + "type": "git", + "path": "strategies/etf_factor_rotation/variants/code/weekend_close_signal_next_open_variant.py" + }, + "owner": "research-platform" + } +} \ No newline at end of file