diff --git a/.github/workflows/drift-check.yml b/.github/workflows/drift-check.yml index f1cede1..ab66fca 100644 --- a/.github/workflows/drift-check.yml +++ b/.github/workflows/drift-check.yml @@ -23,6 +23,7 @@ jobs: timeout-minutes: 15 outputs: snapshot_repository_ref: ${{ steps.snapshot-input.outputs.snapshot_repository_ref }} + lifecycle_profiles: ${{ steps.build-lifecycle.outputs.lifecycle_profiles }} steps: - name: Checkout uses: actions/checkout@v6 @@ -139,6 +140,7 @@ jobs: PY - name: Build lifecycle preflight bundle + id: build-lifecycle env: LIFECYCLE_PREFLIGHT_BUNDLE_ROOT: ${{ runner.temp }}/lifecycle-preflight-bundle MARKET_HISTORY_PATH: ${{ runner.temp }}/snapshot-input/downloaded_price_history.csv @@ -172,6 +174,13 @@ jobs: text=True, ) )["profiles"] + if not profiles: + raise SystemExit("lifecycle preflight profile list is empty") + # Doctor/monitor/drift must only cover profiles that this preflight + # actually materializes. TQQQ stays on its dedicated P1/P3 lane. + output_file = Path(os.environ["GITHUB_OUTPUT"]) + with output_file.open("a", encoding="utf-8") as handle: + handle.write(f"lifecycle_profiles={json.dumps(profiles, separators=(',', ':'))}\n") bundle_root = Path(os.environ["LIFECYCLE_PREFLIGHT_BUNDLE_ROOT"]) market_history = os.environ["MARKET_HISTORY_PATH"] store_root = bundle_root / "data" / "lifecycle_store" @@ -215,6 +224,12 @@ jobs: contents: read issues: write id-token: write + strategy: + fail-fast: false + matrix: + # Scope reusable doctor/monitor/drift to preflight-materialized profiles only. + # Keeps tqqq_growth_income on its dedicated research lane. + profile: ${{ fromJSON(needs.preflight_backtests.outputs.lifecycle_profiles) }} uses: QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@5d6c6bcd970b4a179ccbd21a195e273b17fb59ef with: strategy_domain: us_equity @@ -227,7 +242,7 @@ jobs: lifecycle_performance_bucket: ${{ vars.LIFECYCLE_PERFORMANCE_BUCKET }} quant_platform_kit_ref: 5d6c6bcd970b4a179ccbd21a195e273b17fb59ef lifecycle_preflight_artifact: lifecycle-preflight-${{ github.run_id }}-${{ github.run_attempt }} - strategy_profile: ${{ inputs.strategy_profile || '' }} + strategy_profile: ${{ matrix.profile }} gcp_workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} gcp_service_account: ${{ vars.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }} gcp_project_id: ${{ vars.GCP_PROJECT_ID }} diff --git a/docs/research/global_etf_absolute_volatility.md b/docs/research/global_etf_absolute_volatility.md index 4c8be7f..c97fa8f 100644 --- a/docs/research/global_etf_absolute_volatility.md +++ b/docs/research/global_etf_absolute_volatility.md @@ -5,7 +5,24 @@ 在既有 history 回测入口 `UsEtfRotationBacktestRunner.run` 的 params 中显式设置 `research_absolute_volatility=True` 才启用;默认及 False 完全沿用原策略。 字符串、数字不能替代布尔开关。生产 snapshot、manifest、交易入口不消费此模块。 -本批实现固定 126 个交易日收益、15% 年化目标,不搜索参数;这不构成 OOS 冻结、晋级或交易授权。 +本批实现固定 126 个交易日收益、15% 年化目标,不搜索参数;人工候选工程实现不构成晋级或交易授权。 + +## 未来独立 OOS 合同(2026-09-18 冻结) + +在启动日前锁定以下合同;到期后不得事后改参、换基准或放宽通过规则。 + +| 项 | 冻结值 | +|---|---| +| 候选 | UES `global_etf_absolute_volatility`(`research_absolute_volatility=True`) | +| 窗口 | **2026-10-01** 至 **2027-09-30**(含端点按既有 session/日历语义) | +| 费用 | 默认 **10 bps**(买卖全腿,复用既有模拟器) | +| 比较基准 | 原策略(开关关闭)、**VOO**、**BIL** | +| 记录指标 | 波动、最大回撤、收益代价;允许 `NO_IMPROVEMENT` / `INCOMPLETE` | +| 权限 | `research_only` / `no_order`;不授予 shadow、paper、live | +| `locked` | **true**(相对本未来窗口;2017–2024 learning 仍非本 OOS) | + +未到窗口开始前:可只读核验输入/代码身份,不得用历史 learning 回填本窗口,不得为通过调参。 +AI 真模型评审仍须另授权与额度条件;本冻结不触发 `resume_deferred`。 ## 计算 @@ -26,7 +43,7 @@ BIL 仍有收益、波动和交易成本。窗口和目标记录在回测结果 [Moreira–Muir 原论文](https://amoreira2.github.io/alan-moreira.github.io/VolPortfolios_published.pdf)。 原论文采用上月方差倒数缩放,本候选不是其直接复现,也不能承接其收益结论。 [Cederburg 等的研究](https://www.lehigh.edu/~xuy219/research/COWY.pdf)提供样本外未系统胜出的反向证据。 -作者主页已由 VPS 做过一次只读可用性核查,尚未接入 AAB 的固定来源契约;旧 NBER 案例仍停止。 +固定作者摘要已接入 AAB;[run35136606479](https://github.com/QuantStrategyLab/AIAuditBridge/actions/runs/35136606479) 认证通过,因 quota deferred 未执行模型。本候选由人工实现;AI 评审与金融验收仍待完成;旧 NBER 案例仍停止。 ## 验证与限制 @@ -35,5 +52,5 @@ BIL 仍有收益、波动和交易成本。窗口和目标记录在回测结果 - 复用既有模拟器的现金、份额漂移和买卖全腿费用;当前默认费用为 10 bps。 - runner 按月末检查,原策略在季度月末正常调仓,不宣称每日波动保护。 - close-only lag-one:以 d 收盘信号和 d 收盘价格模拟 d→d+1 持仓,属于研究近似,不证明实时可成交。 -- 后续真实比较须使用已批准云端输入,比较原策略、VOO、BIL 的波动、回撤及收益代价;允许 NO_IMPROVEMENT 或 INCOMPLETE。 -- 本批没有真实行情回测、参数择优、独立样本外验收或模型生成代码试验。人工工程实现不等于系统 AI 联网设计已成功。 +- 真实比较按上方已冻结未来 OOS 合同执行;2017–2024 learning / VOO·BIL 比较不得改称本窗口 OOS。 +- 本批没有真实行情回测、参数择优或模型生成代码试验。人工工程实现不等于系统 AI 联网设计已成功。 diff --git a/tests/test_drift_workflow_config.py b/tests/test_drift_workflow_config.py index 8338504..db01492 100644 --- a/tests/test_drift_workflow_config.py +++ b/tests/test_drift_workflow_config.py @@ -47,5 +47,14 @@ def test_drift_workflow_wires_real_snapshot_history_and_preflight_bundle() -> No assert "REQUESTED_STRATEGY_PROFILE: ${{ inputs.strategy_profile || '' }}" in workflow assert '"--list-profiles"' in workflow assert "unsupported targeted lifecycle profile=" in workflow + assert "id: build-lifecycle" in workflow + assert "lifecycle_profiles: ${{ steps.build-lifecycle.outputs.lifecycle_profiles }}" in workflow + assert "lifecycle_profiles=${json.dumps(profiles, separators=(',', ':'))}" in workflow or ( + 'handle.write(f"lifecycle_profiles={json.dumps(profiles, separators=(\',\', \':\'))}\\n")' in workflow + ) + assert "matrix:" in workflow + assert "profile: ${{ fromJSON(needs.preflight_backtests.outputs.lifecycle_profiles) }}" in workflow + assert "strategy_profile: ${{ matrix.profile }}" in workflow + assert "tqqq_growth_income" not in workflow.split("LIFECYCLE_PREFLIGHT_BUNDLE_ROOT", 1)[1].split("Upload lifecycle", 1)[0] assert "codex_audit_service_url: ${{ secrets.CODEX_AUDIT_SERVICE_URL }}" in workflow assert "secrets.SNAPSHOT_REPOSITORY_TOKEN || secrets.QSL_REPO_SYNC_TOKEN || github.token" in workflow