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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ops/quant-monitor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ Token 从 GCP Secret `quant-sentinel-telegram-bot-token` 加载;**不要**把
监控证据只触发研究审查,不自动修改策略代码、live 参数、仓位、风险预算,不自动
merge 或 deploy。成功记录策略劣化后,monitor 正常结束,不再重复通知人工。

因此健康卡片里的 `canary_eligible` 和
`pause_request_pending_confirmation` 是**证据状态**,不是券商侧已执行的事实。
只有日后接入并回传执行回执的预授权运行时,才能把暂停或 canary 推进标为已完成。

| 变量 | 说明 |
|------|------|
| `QUANT_SENTINEL_TELEGRAM_SECRET_NAME` | 默认 `quant-sentinel-telegram-bot-token` |
Expand Down
14 changes: 7 additions & 7 deletions ops/quant-monitor/scripts/build_dashboard_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
MAX_STRATEGIES = 100
DECISIONS = {
"healthy": {
"code": "auto_advance",
"label": "系统可自动推进下一阶段",
"reason": "机器检查通过;仅在预批准的 canary 预算内推进,不自动进入正常 live。",
"code": "canary_eligible",
"label": "满足 canary 条件,等待预授权执行器确认",
"reason": "机器检查通过;监控只生成证据。仅已接入的预授权 canary 执行器才能推进,绝不自动进入正常 live。",
},
"watch": {
"code": "stay_shadow",
Expand All @@ -39,9 +39,9 @@
"reason": "健康度进入复核区,先确认数据、成本和风险。",
},
"critical": {
"code": "auto_pause",
"label": "自动暂停 / 回滚复核",
"reason": "触发严重告警,系统先控制风险,不等待人工确认。",
"code": "pause_request_pending_confirmation",
"label": "已提出暂停请求,待实际运行时确认",
"reason": "触发严重告警;监控会隔离晋级并生成可审计的暂停请求。缺少运行时回执时,券商执行状态为未知。",
},
}

Expand Down Expand Up @@ -181,7 +181,7 @@ def _decision(status: str, requested_stage: str | None = None) -> dict[str, str]
return {
"code": "human_live_gate",
"label": "机器检查通过,等待你批准正常 live",
"reason": "进入正常资金暴露前仍需人工确认,不会因健康分自动上线。",
"reason": "进入正常资金暴露前仍需人工确认;健康监控本身不具备下单、暂停或上线权限。",
}
return dict(DECISIONS.get(status, {
"code": "evidence_missing",
Expand Down
19 changes: 18 additions & 1 deletion ops/quant-monitor/tests/test_dashboard_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_normalizes_health_scores_and_review_evidence(self):
self.assertEqual(payload["data_status"], "ready")
self.assertEqual(payload["summary"]["healthy"], 1)
self.assertEqual(strategy["review"]["requested_stage"], "shadow_candidate")
self.assertEqual(strategy["decision"]["code"], "auto_advance")
self.assertEqual(strategy["decision"]["code"], "canary_eligible")
self.assertEqual(strategy["freshness"]["status"], "unknown")
self.assertEqual(strategy["source_revision"], "https://example.invalid/revisions/rev-1")
self.assertEqual(strategy["review"]["evidence_package_id"], "https://example.invalid/evidence-1")
Expand Down Expand Up @@ -272,6 +272,23 @@ def test_healthy_live_candidate_still_waits_for_human(self):

self.assertEqual(payload["strategies"][0]["decision"]["code"], "human_live_gate")

def test_critical_status_never_claims_an_unconfirmed_broker_pause(self):
with tempfile.TemporaryDirectory() as tmp:
health = Path(tmp) / "health.json"
health.write_text(json.dumps({"strategies": [{
"strategy_profile": "critical_strategy",
"domain": "us_equity",
"status": "critical",
"overall_score": 12,
}]}), encoding="utf-8")

payload = build_payload(health_file=health)

decision = payload["strategies"][0]["decision"]
self.assertEqual(decision["code"], "pause_request_pending_confirmation")
self.assertIn("待实际运行时确认", decision["label"])
self.assertNotIn("已停单", decision["reason"])


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