From 97370926d68399fd92fb879d503abd1d28ecc40b Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 18 Jun 2026 17:41:04 +0800 Subject: [PATCH] feat(risk): show live countdown on freeze status badges Expose freeze_until_ms from risk API and tick hub/instance badges with remaining 1h/4h/daily time. Co-authored-by: Cursor --- account_risk_lib.py | 38 ++++++++ crypto_monitor_binance/app.py | 12 ++- crypto_monitor_binance/templates/index.html | 18 ++-- crypto_monitor_gate/app.py | 12 ++- crypto_monitor_gate/templates/index.html | 18 ++-- crypto_monitor_gate_bot/app.py | 12 ++- crypto_monitor_gate_bot/templates/index.html | 18 ++-- crypto_monitor_okx/app.py | 12 ++- crypto_monitor_okx/templates/index.html | 18 ++-- docs/account-risk-cooldown.md | 6 +- manual_trading_hub/hub.py | 12 +++ manual_trading_hub/static/app.js | 2 + manual_trading_hub/static/index.html | 5 +- static/account_risk_badge.js | 98 ++++++++++++++++++++ tests/test_account_risk_lib.py | 32 +++++++ 15 files changed, 272 insertions(+), 41 deletions(-) create mode 100644 static/account_risk_badge.js diff --git a/account_risk_lib.py b/account_risk_lib.py index 4fa1c14..2bc137e 100644 --- a/account_risk_lib.py +++ b/account_risk_lib.py @@ -405,6 +405,44 @@ def apply_manual_close_journal_cooloff( ) +def _next_trading_day_reset_ms(now: datetime, reset_hour: int) -> int: + from datetime import timedelta + + h = max(0, min(23, int(reset_hour))) + candidate = now.replace(hour=h, minute=0, second=0, microsecond=0) + if now >= candidate: + candidate = candidate + timedelta(days=1) + return _now_ms(candidate) + + +def enrich_risk_status_countdown( + st: dict[str, Any], + *, + now: Optional[datetime] = None, + daily_reset_hour: int = 8, +) -> dict[str, Any]: + """补充 freeze_until_ms / freeze_remaining_sec,供前端倒计时展示。""" + if not st.get("enabled", True): + return st + dt = now or datetime.now() + now_ms = _now_ms(dt) + until_ms: Optional[int] = None + if st.get("daily_frozen"): + until_ms = _next_trading_day_reset_ms(dt, daily_reset_hour) + elif st.get("cooloff_until_ms"): + try: + until_ms = int(st["cooloff_until_ms"]) + except (TypeError, ValueError): + until_ms = None + if until_ms is not None and until_ms > now_ms: + st["freeze_until_ms"] = until_ms + st["freeze_remaining_sec"] = max(0, (until_ms - now_ms) // 1000) + else: + st["freeze_until_ms"] = None + st["freeze_remaining_sec"] = 0 + return st + + def compute_account_risk_status( conn, *, diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index ac64e92..2841336 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -1538,15 +1538,21 @@ def get_db(): def hub_account_risk_status(conn): - from account_risk_lib import compute_account_risk_status, ensure_account_risk_schema + from account_risk_lib import ( + compute_account_risk_status, + enrich_risk_status_countdown, + ensure_account_risk_schema, + ) ensure_account_risk_schema(conn) - return compute_account_risk_status( + now = app_now() + st = compute_account_risk_status( conn, trading_day=get_trading_day(), - now=app_now(), + now=now, fmt_local_ms=ms_to_app_local_str, ) + return enrich_risk_status_countdown(st, now=now, daily_reset_hour=TRADING_DAY_RESET_HOUR) def hub_user_initiated_close( diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index 3eb2b9c..28e22be 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -5,7 +5,8 @@ - + + @@ -264,7 +265,7 @@

加密货币|交易监控 + AI复盘一体化

{{ exchange_display }}
- {{ risk_status.status_label|default('正常') }} + {{ risk_status.status_label|default('正常') }}