diff --git a/app.py b/app.py index 440461c..96cc492 100644 --- a/app.py +++ b/app.py @@ -5134,6 +5134,16 @@ def render_main_page(page="options", embed_mode=None): records = [] total = rate = 0 profit_loss_ratio = None + if page == "options" and OKX_OPTIONS_ENABLED: + try: + from lib.options.options_stats_lib import header_options_stats_for_window + + opt_header = header_options_stats_for_window(conn, list_window, APP_TZ) + total = opt_header["total"] + rate = opt_header["rate"] + profit_loss_ratio = opt_header.get("profit_loss_ratio") + except Exception: + pass active_count = len(order_list) from lib.trade.trade_labels_lib import count_position_limit_active_monitors @@ -5482,6 +5492,16 @@ def api_account_snapshot(): ) header_trade_stats = header_trade_stats_for_window(conn, _list_window_from_request(), APP_TZ) + page_hint = (request.args.get("page") or "").strip().lower() + if page_hint == "options" and OKX_OPTIONS_ENABLED: + try: + from lib.options.options_stats_lib import header_options_stats_for_window + + header_trade_stats = header_options_stats_for_window( + conn, _list_window_from_request(), APP_TZ + ) + except Exception: + pass conn.close() open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR from lib.trade.open_trade_gate_lib import resolve_manual_open_gate @@ -5582,6 +5602,7 @@ def api_account_snapshot(): "total": header_trade_stats["total"], "rate": header_trade_stats["rate"], "profit_loss_ratio": header_trade_stats.get("profit_loss_ratio"), + "updated_at": app_now_str(), "risk_status": risk_status, **force_close_template_context( FORCE_CLOSE_ENABLED, diff --git a/lib/common/static/instance_embed.js b/lib/common/static/instance_embed.js index be8b93b..b9d2def 100644 --- a/lib/common/static/instance_embed.js +++ b/lib/common/static/instance_embed.js @@ -356,7 +356,11 @@ function syncShellChrome(tab) { const hideTopBar = - tab === "settings" || tab === "risk_policy" || tab === "system_guide" || tab === "env_config"; + tab === "settings" || + tab === "risk_policy" || + tab === "system_guide" || + tab === "env_config" || + tab === "options_review"; document.querySelectorAll(".instance-top-bar").forEach((el) => { el.hidden = hideTopBar; }); diff --git a/lib/common/static/instance_page.css b/lib/common/static/instance_page.css index a2bfcf5..71cd0f0 100644 --- a/lib/common/static/instance_page.css +++ b/lib/common/static/instance_page.css @@ -82,6 +82,7 @@ .list-item{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:9px;background:#1a2034;border:1px solid #2a3150;border-radius:8px} .btn-del{padding:5px 9px;background:#2f2134;color:#ff7b7b;border-radius:8px;text-decoration:none;font-size:.8rem} .rule-tip{font-size:.8rem;color:#95a2c2;margin-bottom:8px} + .instance-price-bar{display:block;visibility:visible;white-space:nowrap;overflow:visible;line-height:1.4} table{width:100%;border-collapse:collapse} th,td{padding:8px;text-align:left;border-bottom:1px solid #25253b;font-size:.85rem} th{color:#a9a9ff} diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index d38e3b5..ff3eec2 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1175,6 +1175,10 @@ function accountSnapshotFundingMissing(data){ let accountSnapshotRetryCount = 0; function applyAccountSnapshot(data){ if(!data || typeof data !== "object") return; + if(data.updated_at){ + const updatedEl = document.getElementById("price-last-updated"); + if(updatedEl) updatedEl.innerText = data.updated_at; + } if(typeof data.show_perp_funds !== "undefined"){ applyPerpFundsVisibility(data.show_perp_funds); } @@ -1266,7 +1270,11 @@ function applyAccountSnapshot(data){ } function refreshAccountSnapshot(opts){ const options = opts || {}; - const qs = options.force ? "?force=1" : ""; + const params = new URLSearchParams(); + if(options.force) params.set("force", "1"); + const page = (document.body && document.body.getAttribute("data-page")) || ""; + if(page) params.set("page", page); + const qs = params.toString() ? ("?" + params.toString()) : ""; fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{ applyAccountSnapshot(data); if(accountSnapshotFundingMissing(data) && !options.force && accountSnapshotRetryCount < 3){ diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index f2c9be0..47932d9 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -7,7 +7,7 @@ - + @@ -55,7 +55,7 @@ {% include 'instance_header_panel.html' %} - {% if initial_tab not in ('settings', 'risk_policy', 'system_guide', 'env_config') and include_transfer_block %} + {% if initial_tab not in ('settings', 'risk_policy', 'system_guide', 'env_config', 'options_review') and include_transfer_block %} {% include 'instance_top_bar.html' %} {% endif %} @@ -146,7 +146,7 @@ window.__INSTANCE_DISPLAY__ = {{ display | tojson }}; - + diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 2f4ee36..b485fcd 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -18,7 +18,7 @@ {{ pwa_app_name }} - + @@ -178,7 +178,7 @@ {% with msg=get_flashed_messages() %}{% if msg %}
{{ msg[0] }}
{% endif %}{% endwith %} {% include 'instance_header_panel.html' %} - {% if page not in ('settings', 'risk_policy', 'system_guide', 'env_config', 'options', 'options_review', 'hedge_plan') %} + {% if page not in ('settings', 'risk_policy', 'system_guide', 'env_config', 'options_review') %} {% include 'instance_top_bar.html' %} {% endif %} @@ -1441,6 +1441,10 @@ function accountSnapshotFundingMissing(data){ let accountSnapshotRetryCount = 0; function applyAccountSnapshot(data){ if(!data || typeof data !== "object") return; + if(data.updated_at){ + const updatedEl = document.getElementById("price-last-updated"); + if(updatedEl) updatedEl.innerText = data.updated_at; + } if(typeof data.show_perp_funds !== "undefined"){ applyPerpFundsVisibility(data.show_perp_funds); } @@ -1541,7 +1545,11 @@ function applyAccountSnapshot(data){ } function refreshAccountSnapshot(opts){ const options = opts || {}; - const qs = options.force ? "?force=1" : ""; + const params = new URLSearchParams(); + if(options.force) params.set("force", "1"); + const page = (document.body && document.body.getAttribute("data-page")) || ""; + if(page) params.set("page", page); + const qs = params.toString() ? ("?" + params.toString()) : ""; fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{ applyAccountSnapshot(data); if(accountSnapshotFundingMissing(data) && !options.force && accountSnapshotRetryCount < 3){ diff --git a/lib/instance/templates/instance_top_bar.html b/lib/instance/templates/instance_top_bar.html index c6af1f0..d285081 100644 --- a/lib/instance/templates/instance_top_bar.html +++ b/lib/instance/templates/instance_top_bar.html @@ -1,4 +1,4 @@ {# 三所统一顶栏:实时价(划转已移至系统设置;切点前开仓说明见风控说明·交易执行) #} -
+
实时价格更新:--(北京时间 UTC+8)
diff --git a/lib/options/options_stats_lib.py b/lib/options/options_stats_lib.py index 052c43e..163ccb5 100644 --- a/lib/options/options_stats_lib.py +++ b/lib/options/options_stats_lib.py @@ -113,9 +113,12 @@ def compute_options_stats(get_db) -> dict[str, Any]: SELECT created_at FROM options_trades WHERE status = 'open' """ ).fetchall() + return _stats_from_option_trade_rows(closed_rows, open_rows) finally: conn.close() + +def _stats_from_option_trade_rows(closed_rows, open_rows) -> dict[str, Any]: wins: list[float] = [] losses: list[float] = [] win_holds: list[float] = [] @@ -171,3 +174,33 @@ def compute_options_stats(get_db) -> dict[str, Any]: "open_count": len(open_holds), "avg_open_hold_sec": _avg_seconds(open_holds), } + + +def header_options_stats_for_window(conn, list_window: dict[str, Any], app_tz) -> dict[str, Any]: + """顶栏:纯期权(options_trades)总交易/胜率/盈亏比,按列表窗过滤.""" + from lib.common.history_window_lib import utc_window_to_bj_sql_strings + + init_options_tables(conn) + start_bj, end_bj = utc_window_to_bj_sql_strings( + list_window["start_utc"], list_window["end_utc"], app_tz + ) + closed_rows = conn.execute( + """ + SELECT realized_pnl, created_at, closed_at + FROM options_trades + WHERE status = 'closed' + AND realized_pnl IS NOT NULL + AND COALESCE(closed_at, created_at) >= ? + AND COALESCE(closed_at, created_at) <= ? + """, + (start_bj, end_bj), + ).fetchall() + open_rows = conn.execute( + "SELECT created_at FROM options_trades WHERE status = 'open'" + ).fetchall() + stats = _stats_from_option_trade_rows(closed_rows, open_rows) + return { + "total": int(stats.get("total_closed") or 0), + "rate": float(stats.get("win_rate") or 0), + "profit_loss_ratio": stats.get("profit_loss_ratio"), + }