From f27b4bc1ecf965142310959860665c3a464e50a2 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 17 Jul 2026 13:03:04 +0800 Subject: [PATCH] Fix OKX header trade stats stuck at zero after settings shell load. Always SSR records summary on embed shell (including settings/risk/env), and refresh total/win-rate/PL ratio via account_snapshot so soft-nav cannot leave the strip blank. Co-authored-by: Cursor --- crypto_monitor_binance/app.py | 7 ++++++- crypto_monitor_gate/app.py | 7 ++++++- crypto_monitor_okx/app.py | 7 ++++++- lib/instance/instance_embed_context_lib.py | 20 +++++++++++++++++-- .../templates/embed_boot_scripts.html | 14 +++++++++++++ lib/instance/templates/index.html | 14 +++++++++++++ tests/test_instance_embed_context_lib.py | 8 ++++++++ 7 files changed, 72 insertions(+), 5 deletions(-) diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index 0a2adf7..59cc6e7 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -7458,6 +7458,9 @@ def api_account_snapshot(): active_pnl_rows = conn.execute( "SELECT exchange_symbol, symbol, direction FROM order_monitors WHERE status='active'" ).fetchall() + from lib.instance.instance_embed_context_lib import header_trade_stats_for_window, total_funds_usdt + + header_trade_stats = header_trade_stats_for_window(conn, _list_window_from_request(), APP_TZ) conn.close() can_trade = can_trade_new_open( time_allows=trading_day_reset_allows_new_open(now), @@ -7468,7 +7471,6 @@ def api_account_snapshot(): extra_blocks=not risk_status.get("can_trade", True), ) available_trading_usdt = get_available_trading_usdt() - from lib.instance.instance_embed_context_lib import total_funds_usdt unrealized_pnl = None if exchange_private_api_configured(): @@ -7498,6 +7500,9 @@ def api_account_snapshot(): "daily_open_alert_threshold": DAILY_OPEN_ALERT_THRESHOLD, "manual_min_planned_rr": MANUAL_MIN_PLANNED_RR, "trading_day": trading_day, + "total": header_trade_stats["total"], + "rate": header_trade_stats["rate"], + "profit_loss_ratio": header_trade_stats.get("profit_loss_ratio"), "risk_status": risk_status, **force_close_template_context( FORCE_CLOSE_ENABLED, diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 42b4c6b..d5feb6f 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -7274,6 +7274,9 @@ def api_account_snapshot(): active_pnl_rows = conn.execute( "SELECT exchange_symbol, symbol, direction FROM order_monitors WHERE status='active'" ).fetchall() + from lib.instance.instance_embed_context_lib import header_trade_stats_for_window, total_funds_usdt + + header_trade_stats = header_trade_stats_for_window(conn, _list_window_from_request(), APP_TZ) conn.close() can_trade = can_trade_new_open( time_allows=trading_day_reset_allows_new_open(now), @@ -7284,7 +7287,6 @@ def api_account_snapshot(): extra_blocks=not risk_status.get("can_trade", True), ) available_trading_usdt = get_available_trading_usdt() - from lib.instance.instance_embed_context_lib import total_funds_usdt unrealized_pnl = None if exchange_private_api_configured(): @@ -7317,6 +7319,9 @@ def api_account_snapshot(): "daily_open_alert_threshold": DAILY_OPEN_ALERT_THRESHOLD, "manual_min_planned_rr": MANUAL_MIN_PLANNED_RR, "trading_day": trading_day, + "total": header_trade_stats["total"], + "rate": header_trade_stats["rate"], + "profit_loss_ratio": header_trade_stats.get("profit_loss_ratio"), "risk_status": risk_status, **force_close_template_context( FORCE_CLOSE_ENABLED, diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 197c15f..cc0971f 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -6942,6 +6942,9 @@ def api_account_snapshot(): active_pnl_rows = conn.execute( "SELECT exchange_symbol, symbol, direction FROM order_monitors WHERE status='active'" ).fetchall() + from lib.instance.instance_embed_context_lib import header_trade_stats_for_window, total_funds_usdt + + header_trade_stats = header_trade_stats_for_window(conn, _list_window_from_request(), APP_TZ) conn.close() open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR can_trade = can_trade_new_open( @@ -6953,7 +6956,6 @@ def api_account_snapshot(): extra_blocks=not risk_status.get("can_trade", True), ) available_trading_usdt = get_available_trading_usdt() - from lib.instance.instance_embed_context_lib import total_funds_usdt unrealized_pnl = None if exchange_private_api_configured(): @@ -7021,6 +7023,9 @@ def api_account_snapshot(): "reset_hour": TRADING_DAY_RESET_HOUR, "manual_min_planned_rr": MANUAL_MIN_PLANNED_RR, "trading_day": trading_day, + "total": header_trade_stats["total"], + "rate": header_trade_stats["rate"], + "profit_loss_ratio": header_trade_stats.get("profit_loss_ratio"), "risk_status": risk_status, **force_close_template_context( FORCE_CLOSE_ENABLED, diff --git a/lib/instance/instance_embed_context_lib.py b/lib/instance/instance_embed_context_lib.py index 563467f..22599e5 100644 --- a/lib/instance/instance_embed_context_lib.py +++ b/lib/instance/instance_embed_context_lib.py @@ -38,11 +38,11 @@ def embed_render_plan(page: str, embed_mode: str | None) -> EmbedRenderPlan: ) is_shell = embed_mode == "shell" is_strategy = page in EMBED_STRATEGY_PAGES - is_settings_like = page in ("settings", "risk_policy", "env_config") return EmbedRenderPlan( exchange_capitals=is_shell, records_rows=page == "records", - records_summary=is_shell and page != "records" and not is_settings_like, + # 顶栏常驻:设置/风控/env 也要统计,否则首屏 SSR 为 0 后软切 tab 不会重绘顶栏 + records_summary=is_shell and page != "records", key_history=page == "key_monitor", key_list=page in ("key_monitor", "trade") or is_strategy, orders=page == "trade" or is_strategy, @@ -157,5 +157,21 @@ def trade_records_summary(conn, start_bj: str, end_bj: str, tr_ts: str) -> dict[ } +def header_trade_stats_for_window(conn, list_window: dict[str, Any], app_tz) -> dict[str, Any]: + """account_snapshot / 顶栏刷新:按当前列表窗返回总交易/胜率/盈亏比.""" + from lib.common.history_window_lib import sql_list_time_field, utc_window_to_bj_sql_strings + + start_bj, end_bj = utc_window_to_bj_sql_strings( + list_window["start_utc"], list_window["end_utc"], app_tz + ) + tr_ts = sql_list_time_field("closed_at", "created_at", "opened_at") + summary = trade_records_summary(conn, start_bj, end_bj, tr_ts) + return { + "total": summary["total"], + "rate": summary["rate"], + "profit_loss_ratio": summary.get("profit_loss_ratio"), + } + + def minimal_stats_bundle(reset_hour: int) -> dict[str, Any]: return {"stats_reset_hour": reset_hour, "segments": []} diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index 9a32dd2..bad048d 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1178,6 +1178,20 @@ function applyAccountSnapshot(data){ if(typeof data.unrealized_pnl !== "undefined"){ updateRealtimePnl(data.unrealized_pnl); } + if(typeof data.total !== "undefined" && data.total !== null){ + setFundsFieldText("stat-total", String(data.total)); + } + if(typeof data.rate !== "undefined" && data.rate !== null){ + setFundsFieldText("stat-rate", `${Number(data.rate)}%`); + } + if(typeof data.profit_loss_ratio !== "undefined"){ + setFundsFieldText( + "stat-pl-ratio", + data.profit_loss_ratio != null && data.profit_loss_ratio !== "" + ? String(data.profit_loss_ratio) + : "—" + ); + } if(typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null){ latestAvailableUsdt = Number(data.available_trading_usdt); } diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index ad4e385..7892546 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -1659,6 +1659,20 @@ function applyAccountSnapshot(data){ if(typeof data.unrealized_pnl !== "undefined"){ updateRealtimePnl(data.unrealized_pnl); } + if(typeof data.total !== "undefined" && data.total !== null){ + setFundsFieldText("stat-total", String(data.total)); + } + if(typeof data.rate !== "undefined" && data.rate !== null){ + setFundsFieldText("stat-rate", `${Number(data.rate)}%`); + } + if(typeof data.profit_loss_ratio !== "undefined"){ + setFundsFieldText( + "stat-pl-ratio", + data.profit_loss_ratio != null && data.profit_loss_ratio !== "" + ? String(data.profit_loss_ratio) + : "—" + ); + } if(typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null){ latestAvailableUsdt = Number(data.available_trading_usdt); } diff --git a/tests/test_instance_embed_context_lib.py b/tests/test_instance_embed_context_lib.py index 22683d9..b9b18cc 100644 --- a/tests/test_instance_embed_context_lib.py +++ b/tests/test_instance_embed_context_lib.py @@ -17,6 +17,14 @@ def test_embed_shell_trade_summary_only(): assert plan.records_rows is False +def test_embed_shell_settings_still_loads_header_summary(): + plan = embed_render_plan("settings", "shell") + assert plan.records_summary is True + assert plan.records_rows is False + plan_risk = embed_render_plan("risk_policy", "shell") + assert plan_risk.records_summary is True + + def test_embed_records_page_loads_rows(): plan = embed_render_plan("records", "fragment") assert plan.records_rows is True