diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 63a48b5..1194d47 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -6494,6 +6494,7 @@ def render_main_page(page="trade", embed_mode=None): from lib.instance.instance_embed_context_lib import ( embed_render_plan, minimal_stats_bundle, + options_funding_label, profit_loss_ratio_from_trades, total_funds_usdt, trade_records_summary, @@ -6508,15 +6509,22 @@ def render_main_page(page="trade", embed_mode=None): current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS) options_trading_usdc = None options_funding_usdc = None + options_funding_usdt = None if OKX_OPTIONS_ENABLED and exchange_options.apiKey: try: - from lib.exchange.okx_options_lib import fetch_options_funding_usdc, fetch_options_trading_usdc + from lib.exchange.okx_options_lib import ( + fetch_options_funding_usdc, + fetch_options_funding_usdt, + fetch_options_trading_usdc, + ) options_trading_usdc = fetch_options_trading_usdc(exchange_options) options_funding_usdc = fetch_options_funding_usdc(exchange_options) + options_funding_usdt = fetch_options_funding_usdt(exchange_options) except Exception: options_trading_usdc = None options_funding_usdc = None + options_funding_usdt = None recommended_capital = get_recommended_capital(current_capital) key_list = ( conn.execute("SELECT * FROM key_monitors").fetchall() if plan.key_list else [] @@ -6635,9 +6643,14 @@ def render_main_page(page="trade", embed_mode=None): rate=rate, profit_loss_ratio=profit_loss_ratio, total_funds=total_funds_usdt( - funding_usdt, current_capital, options_trading_usdc, options_funding_usdc + funding_usdt, + current_capital, + options_trading_usdc, + options_funding_usdc, + options_funding_usdt, ), options_funding_usdc=options_funding_usdc, + options_funding_usdt=options_funding_usdt, options_trading_usdc=options_trading_usdc, trading_day=trading_day, daily_start_capital=DAILY_START_CAPITAL, @@ -6697,6 +6710,7 @@ def render_main_page(page="trade", embed_mode=None): journal_chart_default_anchor=JOURNAL_CHART_DEFAULT_ANCHOR, key_rule_ctx=key_rule_ctx, funds_fmt=format_funds_u, + options_funding_label=options_funding_label, exchange_display=EXCHANGE_DISPLAY_NAME, options_enabled=OKX_OPTIONS_ENABLED, options_nav_visible=True, @@ -6800,15 +6814,22 @@ def api_account_snapshot(): current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS) options_trading_usdc = None options_funding_usdc = None + options_funding_usdt = None if OKX_OPTIONS_ENABLED and exchange_options.apiKey: try: - from lib.exchange.okx_options_lib import fetch_options_funding_usdc, fetch_options_trading_usdc + from lib.exchange.okx_options_lib import ( + fetch_options_funding_usdc, + fetch_options_funding_usdt, + fetch_options_trading_usdc, + ) options_trading_usdc = fetch_options_trading_usdc(exchange_options) options_funding_usdc = fetch_options_funding_usdc(exchange_options) + options_funding_usdt = fetch_options_funding_usdt(exchange_options) except Exception: options_trading_usdc = None options_funding_usdc = None + options_funding_usdt = None recommended_capital = get_recommended_capital(current_capital) from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors @@ -6852,9 +6873,14 @@ def api_account_snapshot(): "funding_usdt": funding_usdt, "current_capital": current_capital, "options_funding_usdc": options_funding_usdc, + "options_funding_usdt": options_funding_usdt, "options_trading_usdc": options_trading_usdc, "total_funds": total_funds_usdt( - funding_usdt, current_capital, options_trading_usdc, options_funding_usdc + funding_usdt, + current_capital, + options_trading_usdc, + options_funding_usdc, + options_funding_usdt, ), "available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None, "unrealized_pnl": unrealized_pnl, diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index d5e4e16..2a2ea12 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -96,7 +96,7 @@ } .instance-header-stats { - grid-template-columns: repeat(2, minmax(0, 1fr)); + flex-wrap: nowrap; } .stat-strip-inner { @@ -602,15 +602,25 @@ html[data-theme="light"] .theme-toggle-btn.is-active { font-size: 0.82rem; } -.instance-header-stats { - display: grid; - grid-template-columns: repeat(9, minmax(0, 1fr)); - gap: 0; +.instance-header-stats-wrap { margin-top: 12px; - padding: 14px 0 12px; + padding-top: 12px; border-top: 1px solid var(--border-soft, #2a3150); - min-height: 72px; - align-items: center; +} + +.instance-header-stats { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + gap: 0; + padding: 2px 0 0; + min-height: 56px; + align-items: stretch; + scrollbar-width: thin; +} + +.instance-header-stats--options .stat-strip-item { + min-width: 72px; } .stat-strip-item { @@ -618,8 +628,9 @@ html[data-theme="light"] .theme-toggle-btn.is-active { flex-direction: column; align-items: center; justify-content: center; - min-width: 0; - padding: 4px 10px; + flex: 1 1 0; + min-width: 64px; + padding: 4px 8px; border-right: 1px solid var(--border-soft, #2a3150); text-align: center; } @@ -641,7 +652,7 @@ html[data-theme="light"] .theme-toggle-btn.is-active { } .stat-strip-item .value { - font-size: 0.94rem; + font-size: 0.88rem; font-weight: 600; color: #e8ecff; line-height: 1.3; @@ -653,7 +664,7 @@ html[data-theme="light"] .theme-toggle-btn.is-active { } .stat-strip-item--primary .value { - font-size: 1.18rem; + font-size: 1.02rem; font-weight: 700; } @@ -667,13 +678,15 @@ html[data-theme="light"] .theme-toggle-btn.is-active { @media (max-width: 1100px) { .instance-header-stats { - grid-template-columns: repeat(3, minmax(0, 1fr)); - row-gap: 10px; + flex-wrap: nowrap; } .stat-strip-item { + flex: 0 0 auto; + min-width: 72px; border-right: none; padding: 6px 8px; + border-right: 1px solid var(--border-soft, #2a3150); } .stat-strip-item:first-child { @@ -2149,6 +2162,26 @@ html[data-theme="light"] .journal-detail-img-thumb { margin-top: 12px; } +.settings-account-summary { + margin-bottom: 16px; + padding: 12px 14px 10px; +} + +.settings-account-summary .instance-header-stats { + border-top: none; + margin-top: 0; + padding-top: 4px; +} + +.settings-account-summary-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 8px; + flex-wrap: wrap; +} + .settings-cards-grid { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(280px, 1fr); diff --git a/lib/exchange/okx_options_lib.py b/lib/exchange/okx_options_lib.py index a0969b3..e408756 100644 --- a/lib/exchange/okx_options_lib.py +++ b/lib/exchange/okx_options_lib.py @@ -497,6 +497,14 @@ def fetch_options_funding_usdc(ex: ccxt.okx) -> float | None: return round(float(v), 2) +def fetch_options_funding_usdt(ex: ccxt.okx) -> float | None: + bal = fetch_options_balances(ex) + v = bal.get("funding_usdt") + if v is None: + return None + return round(float(v), 2) + + def spot_market_swap_usdt_usdc( ex: ccxt.okx, *, diff --git a/lib/instance/instance_embed_context_lib.py b/lib/instance/instance_embed_context_lib.py index ac971ef..3178a5f 100644 --- a/lib/instance/instance_embed_context_lib.py +++ b/lib/instance/instance_embed_context_lib.py @@ -85,11 +85,24 @@ def profit_loss_ratio_from_trades(trades: list[dict[str, Any]] | None) -> float return profit_loss_ratio_from_averages(avg_win, avg_loss) +def options_funding_label( + funding_usdc: float | None, + funding_usdt: float | None = None, +) -> str: + parts: list[str] = [] + if funding_usdc is not None: + parts.append(f"{float(funding_usdc):.2f} USDC") + if funding_usdt is not None: + parts.append(f"{float(funding_usdt):.2f} USDT") + return " · ".join(parts) if parts else "—" + + def total_funds_usdt( funding_usdt: float | None, trading_usdt: float | None, options_trading_usdc: float | None = None, options_funding_usdc: float | None = None, + options_funding_usdt: float | None = None, ) -> float | None: if funding_usdt is None: return None @@ -97,6 +110,8 @@ def total_funds_usdt( total = float(funding_usdt) + float(trading_usdt or 0) if options_funding_usdc is not None: total += float(options_funding_usdc) + if options_funding_usdt is not None: + total += float(options_funding_usdt) if options_trading_usdc is not None: total += float(options_trading_usdc) return round(total, 2) diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index be6ee33..21dd3aa 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1047,6 +1047,13 @@ function sumOrdersFloatPnl(orders){ return found ? total : null; } +function formatOptionsFundingLabel(usdc, usdt) { + const parts = []; + if (usdc !== null && usdc !== undefined) parts.push(`${Number(usdc).toFixed(2)} USDC`); + if (usdt !== null && usdt !== undefined) parts.push(`${Number(usdt).toFixed(2)} USDT`); + return parts.length ? parts.join(" · ") : "—"; +} + function refreshAccountSnapshot(){ fetch("/api/account_snapshot").then(r=>r.json()).then(data=>{ if (typeof data.funding_usdt !== "undefined") { @@ -1061,10 +1068,9 @@ function refreshAccountSnapshot(){ const el = document.getElementById("current-capital"); if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`; } - if (typeof data.options_funding_usdc !== "undefined") { + if (typeof data.options_funding_usdc !== "undefined" || typeof data.options_funding_usdt !== "undefined") { const el = document.getElementById("options-funding-usdc"); - if (el) el.innerText = (data.options_funding_usdc === null || data.options_funding_usdc === undefined) - ? "—" : `${Number(data.options_funding_usdc).toFixed(2)} USDC`; + if (el) el.innerText = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt); } if (typeof data.options_trading_usdc !== "undefined") { const el = document.getElementById("options-trading-usdc"); @@ -1179,6 +1185,8 @@ if(window.ManualOrderRrPreview){ }); refreshAccountSnapshot(); +const settingsRefreshFunds = document.getElementById("settings-refresh-funds"); +if (settingsRefreshFunds) settingsRefreshFunds.addEventListener("click", refreshAccountSnapshot); if (window.AccountRiskBadge) AccountRiskBadge.startTicker(); const _journalFormEl = document.getElementById("journal-form"); if(_journalFormEl){ diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index 4ba7300..3d890ec 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -7,7 +7,7 @@ - +