Add OKX env toggle to show or hide perpetual funds in the header.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-05 17:55:15 +08:00
parent 55e94fe059
commit 22e6b68e6d
11 changed files with 105 additions and 10 deletions
+2
View File
@@ -99,6 +99,8 @@ OKX_POSITION_INST_TYPE=SWAP
EXCHANGE_DISPLAY_NAME=OKX
# 企业微信推送里展示的账户备注
# OKX_ACCOUNT_LABEL=
# 顶栏是否显示永续资金账户/交易账户(热更);false 时总资金仅计期权侧
OKX_SHOW_PERP_FUNDS=true
# =============================================================================
# 期权(主账户 API,与永续子账户 OKX_API_* 分离;修改后须重启 PM2)
+14 -5
View File
@@ -6587,6 +6587,7 @@ def render_main_page(page="trade", embed_mode=None):
minimal_stats_bundle,
options_funding_label,
profit_loss_ratio_from_trades,
show_perp_funds_enabled,
total_funds_usdt,
trade_records_summary,
)
@@ -6738,6 +6739,7 @@ def render_main_page(page="trade", embed_mode=None):
_okx_trade_mode = get_okx_trade_mode()
_hedge_mode_on = _okx_trade_mode in ("perp_options", "options_options")
_show_perp_funds = show_perp_funds_enabled(exchange_key="okx")
template_ctx = dict(
page=page,
key=key_list,
@@ -6749,8 +6751,8 @@ 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,
funding_usdt if _show_perp_funds else None,
current_capital if _show_perp_funds else None,
options_trading_usdc,
options_funding_usdc,
options_funding_usdt,
@@ -6826,6 +6828,7 @@ def render_main_page(page="trade", embed_mode=None):
options_funding_label=options_funding_label,
exchange_display=EXCHANGE_DISPLAY_NAME,
options_enabled=OKX_OPTIONS_ENABLED,
show_perp_funds=_show_perp_funds,
options_nav_visible=True,
okx_trade_mode=_okx_trade_mode,
options_open_allowed=_okx_trade_mode == "options",
@@ -7025,7 +7028,11 @@ 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
from lib.instance.instance_embed_context_lib import (
header_trade_stats_for_window,
show_perp_funds_enabled,
total_funds_usdt,
)
header_trade_stats = header_trade_stats_for_window(conn, _list_window_from_request(), APP_TZ)
conn.close()
@@ -7076,16 +7083,18 @@ def api_account_snapshot():
unrealized_pnl = merge_unrealized_pnl_components(unrealized_pnl, options_unrealized_pnl)
except Exception:
options_unrealized_pnl = None
_show_perp_funds = show_perp_funds_enabled(exchange_key="okx")
return jsonify({
"funding_usdt": funding_usdt,
"current_capital": current_capital,
"show_perp_funds": _show_perp_funds,
"options_funding_usdc": options_funding_usdc,
"options_funding_usdt": options_funding_usdt,
"options_trading_usdc": options_trading_usdc,
"options_trading_usdt": options_trading_usdt,
"total_funds": total_funds_usdt(
funding_usdt,
current_capital,
funding_usdt if _show_perp_funds else None,
current_capital if _show_perp_funds else None,
options_trading_usdc,
options_funding_usdc,
options_funding_usdt,
+1
View File
@@ -68,6 +68,7 @@ Binance / Gate 无期权模块时,第三列最后一格不显示或显示「本
| 持仓模式 | 双向 / 单向净持仓等(按所) | 需重启 |
| 仓位查询类型 | 仅 OKX:如 SWAP | 需重启 |
| 账户备注 | 企业微信推送中显示的交易所备注 | 保存即生效 |
| 显示永续资金 | 仅 OKX:关闭后顶栏隐藏永续资金账户与交易账户,总资金仅计期权侧 | 保存即生效 |
**本卡片不包含**:网页登录账号密码,是否关闭登录校验,中控通信密钥.
+6 -1
View File
@@ -89,6 +89,7 @@ HOT_RELOAD_EXACT = frozenset({
"HEDGE_PLAN_ENABLED",
"HEDGE_PLAN_SHOW_PERP_OPTIONS",
"HEDGE_PLAN_SHOW_OPTIONS_OPTIONS",
"OKX_SHOW_PERP_FUNDS",
"OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED",
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS",
"OKX_TRADE_MODE",
@@ -214,7 +215,11 @@ def _field_type(key: str, value: str) -> str:
low = (value or "").strip().lower()
if low in ("true", "false"):
return "bool"
if key.endswith("_ENABLED") or key.startswith("RISK_MOOD_"):
if key.endswith("_ENABLED") or key.startswith("RISK_MOOD_") or key in (
"OKX_SHOW_PERP_FUNDS",
"HEDGE_PLAN_SHOW_PERP_OPTIONS",
"HEDGE_PLAN_SHOW_OPTIONS_OPTIONS",
):
return "bool"
try:
if "." in low:
+6
View File
@@ -27,6 +27,11 @@ _EXCHANGE_LIVE_FIELDS: dict[str, list[tuple[str, str, str]]] = {
("OKX_POS_MODE", "持仓模式", ""),
("OKX_POSITION_INST_TYPE", "仓位查询类型", "如 SWAP"),
("OKX_ACCOUNT_LABEL", "账户备注", "企业微信推送中显示"),
(
"OKX_SHOW_PERP_FUNDS",
"显示永续资金",
"默认开启;关闭后顶栏隐藏永续资金账户与交易账户,总资金仅计期权侧",
),
],
"binance": [
("LIVE_TRADING_ENABLED", "开启实盘下单", "关闭时仅走本地流程,不向交易所发单"),
@@ -236,6 +241,7 @@ _RUNTIME_ENV_DEFAULTS: dict[str, str] = {
"TRANSFER_CCY": "USDT",
"HEDGE_PLAN_SHOW_PERP_OPTIONS": "true",
"HEDGE_PLAN_SHOW_OPTIONS_OPTIONS": "true",
"OKX_SHOW_PERP_FUNDS": "true",
"OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED": "true",
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS": "0",
"OKX_TRADE_MODE": "options",
@@ -2,6 +2,7 @@
from __future__ import annotations
import os
from dataclasses import dataclass
from typing import Any
@@ -10,6 +11,20 @@ EMBED_STRATEGY_PAGES = frozenset({"strategy", "strategy_trend", "strategy_roll",
_WIN_EPS = 1e-9
def env_truthy(raw: str | None, default: bool = False) -> bool:
if raw is None or str(raw).strip() == "":
return default
return str(raw).strip().lower() in ("1", "true", "yes", "on")
def show_perp_funds_enabled(*, exchange_key: str | None = None) -> bool:
"""OKX:是否在顶栏显示永续资金账户/交易账户.其他所恒为 True."""
ex = (exchange_key or "").strip().lower()
if ex and ex != "okx":
return True
return env_truthy(os.getenv("OKX_SHOW_PERP_FUNDS"), default=True)
@dataclass(frozen=True)
class EmbedRenderPlan:
exchange_capitals: bool
@@ -1149,8 +1149,23 @@ function setFundsFieldText(field, text){
el.innerText = text;
});
}
function applyPerpFundsVisibility(show){
const on = show !== false;
document.querySelectorAll("[data-perp-funds='1']").forEach((el) => {
el.style.display = on ? "" : "none";
});
}
function accountSnapshotFundingMissing(data){
if(!data || typeof data !== "object") return true;
if(data.show_perp_funds === false){
const hasTotal = data.total_funds != null && data.total_funds !== "";
const hasOpt =
data.options_funding_usdc != null ||
data.options_funding_usdt != null ||
data.options_trading_usdc != null ||
data.options_trading_usdt != null;
return !hasTotal && !hasOpt;
}
const hasFunding = data.funding_usdt != null && data.funding_usdt !== "";
const hasTotal = data.total_funds != null && data.total_funds !== "";
const hasTrading = data.current_capital != null && data.current_capital !== "";
@@ -1159,6 +1174,9 @@ function accountSnapshotFundingMissing(data){
let accountSnapshotRetryCount = 0;
function applyAccountSnapshot(data){
if(!data || typeof data !== "object") return;
if(typeof data.show_perp_funds !== "undefined"){
applyPerpFundsVisibility(data.show_perp_funds);
}
if(data.funding_usdt != null && data.funding_usdt !== ""){
setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`);
}
+18
View File
@@ -1626,8 +1626,23 @@ function setFundsFieldText(field, text){
el.innerText = text;
});
}
function applyPerpFundsVisibility(show){
const on = show !== false;
document.querySelectorAll("[data-perp-funds='1']").forEach((el) => {
el.style.display = on ? "" : "none";
});
}
function accountSnapshotFundingMissing(data){
if(!data || typeof data !== "object") return true;
if(data.show_perp_funds === false){
const hasTotal = data.total_funds != null && data.total_funds !== "";
const hasOpt =
data.options_funding_usdc != null ||
data.options_funding_usdt != null ||
data.options_trading_usdc != null ||
data.options_trading_usdt != null;
return !hasTotal && !hasOpt;
}
const hasFunding = data.funding_usdt != null && data.funding_usdt !== "";
const hasTotal = data.total_funds != null && data.total_funds !== "";
const hasTrading = data.current_capital != null && data.current_capital !== "";
@@ -1636,6 +1651,9 @@ function accountSnapshotFundingMissing(data){
let accountSnapshotRetryCount = 0;
function applyAccountSnapshot(data){
if(!data || typeof data !== "object") return;
if(typeof data.show_perp_funds !== "undefined"){
applyPerpFundsVisibility(data.show_perp_funds);
}
if(data.funding_usdt != null && data.funding_usdt !== ""){
setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`);
}
@@ -38,11 +38,11 @@
{% include 'instance_header_stats.html' %}
</div>
<div class="instance-header-phone-strip instance-phone-only" aria-label="手机资金摘要">
<span class="inst-phone-chip">
<span class="inst-phone-chip"{% if not (show_perp_funds|default(true)) %} style="display:none"{% endif %} data-perp-funds="1">
<em>交易</em>
<b data-funds-field="current-capital">{{ funds_fmt(current_capital) }}U</b>
</span>
<span class="inst-phone-chip">
<span class="inst-phone-chip"{% if not (show_perp_funds|default(true)) %} style="display:none"{% endif %} data-perp-funds="1">
<em>资金</em>
<b data-funds-field="total-capital">{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}</b>
</span>
@@ -24,11 +24,11 @@
<div class="label">总资金</div>
<div class="value" id="total-funds" data-funds-field="total-funds">{% if total_funds is not none %}{{ funds_fmt(total_funds) }}U{% else %}—{% endif %}</div>
</div>
<div class="stat-strip-item">
<div class="stat-strip-item"{% if not (show_perp_funds|default(true)) %} style="display:none"{% endif %} data-perp-funds="1">
<div class="label">资金账户</div>
<div class="value" id="total-capital" data-funds-field="total-capital">{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}</div>
</div>
<div class="stat-strip-item">
<div class="stat-strip-item"{% if not (show_perp_funds|default(true)) %} style="display:none"{% endif %} data-perp-funds="1">
<div class="label">交易账户</div>
<div class="value" id="current-capital" data-funds-field="current-capital">{{ funds_fmt(current_capital) }}U</div>
</div>
@@ -108,6 +108,10 @@ class TestEnvSchema(unittest.TestCase):
self.assertEqual(by_key["KEY_AUTO_ORDER_ENABLED"]["label"], "关键位自动单")
self.assertEqual(by_key["KEY_AUTO_ORDER_ENABLED"]["type"], "bool")
self.assertIn("KEY_AUTO_MIN_PLANNED_RR", by_key)
self.assertIn("OKX_SHOW_PERP_FUNDS", by_key)
self.assertEqual(by_key["OKX_SHOW_PERP_FUNDS"]["label"], "显示永续资金")
self.assertEqual(by_key["OKX_SHOW_PERP_FUNDS"]["type"], "bool")
self.assertTrue(by_key["OKX_SHOW_PERP_FUNDS"].get("hot_reload"))
groups_v = [{"title": "t", "fields": [by_key["TRADE_DIRECTION"]]}]
clean, errors = validate_env_updates(groups_v, {"TRADE_DIRECTION": "long_only"})
@@ -117,5 +121,22 @@ class TestEnvSchema(unittest.TestCase):
self.assertTrue(bad)
class TestShowPerpFunds(unittest.TestCase):
def test_okx_default_on(self):
from lib.instance.instance_embed_context_lib import show_perp_funds_enabled
old = os.environ.pop("OKX_SHOW_PERP_FUNDS", None)
try:
self.assertTrue(show_perp_funds_enabled(exchange_key="okx"))
os.environ["OKX_SHOW_PERP_FUNDS"] = "false"
self.assertFalse(show_perp_funds_enabled(exchange_key="okx"))
self.assertTrue(show_perp_funds_enabled(exchange_key="gate"))
finally:
if old is None:
os.environ.pop("OKX_SHOW_PERP_FUNDS", None)
else:
os.environ["OKX_SHOW_PERP_FUNDS"] = old
if __name__ == "__main__":
unittest.main()