Show okx_sim/okx_live and gate live API fields by matching mode.

In sim, hide LIVE/API keys and expose simulated fund settings in env UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-14 18:35:08 +08:00
parent bd742a2e43
commit e7aa724a3b
9 changed files with 150 additions and 20 deletions
+16 -1
View File
@@ -5046,11 +5046,18 @@ def render_main_page(page="options", embed_mode=None):
options_funding_usdt = None options_funding_usdt = None
options_trading_usdt = None options_trading_usdt = None
_sim_mode_for_header = False _sim_mode_for_header = False
_exchange_display_for_header = EXCHANGE_DISPLAY_NAME
try: try:
from lib.sim.mode_lib import exchange_mode_label as _exchange_mode_label
from lib.sim.mode_lib import is_sim_mode as _is_sim_mode_fn from lib.sim.mode_lib import is_sim_mode as _is_sim_mode_fn
_sim_mode_for_header = bool(_is_sim_mode_fn(get_db)) _sim_mode_for_header = bool(_is_sim_mode_fn(get_db))
_exchange_display_for_header = _exchange_mode_label(
is_sim=_sim_mode_for_header, exchange_key="okx"
)
except Exception: except Exception:
_sim_mode_for_header = False _sim_mode_for_header = False
_exchange_display_for_header = EXCHANGE_DISPLAY_NAME
if ( if (
OKX_OPTIONS_ENABLED OKX_OPTIONS_ENABLED
and embed_mode != "fragment" and embed_mode != "fragment"
@@ -5258,7 +5265,7 @@ def render_main_page(page="options", embed_mode=None):
key_rule_ctx=key_rule_ctx, key_rule_ctx=key_rule_ctx,
funds_fmt=format_funds_u, funds_fmt=format_funds_u,
options_funding_label=options_funding_label, options_funding_label=options_funding_label,
exchange_display=EXCHANGE_DISPLAY_NAME, exchange_display=_exchange_display_for_header,
options_enabled=OKX_OPTIONS_ENABLED, options_enabled=OKX_OPTIONS_ENABLED,
trading_mode=("sim" if _sim_mode_for_header else "live"), trading_mode=("sim" if _sim_mode_for_header else "live"),
is_sim_mode=_sim_mode_for_header, is_sim_mode=_sim_mode_for_header,
@@ -5532,10 +5539,18 @@ def api_account_snapshot():
except Exception: except Exception:
options_unrealized_pnl = None options_unrealized_pnl = None
_show_perp_funds = show_perp_funds_enabled(exchange_key="okx") _show_perp_funds = show_perp_funds_enabled(exchange_key="okx")
try:
from lib.sim.mode_lib import exchange_mode_label as _ex_mode_label
from lib.sim.mode_lib import is_sim_mode as _is_sim
_snapshot_mode_label = _ex_mode_label(is_sim=_is_sim(get_db), exchange_key="okx")
except Exception:
_snapshot_mode_label = EXCHANGE_DISPLAY_NAME
return jsonify({ return jsonify({
"funding_usdt": funding_usdt, "funding_usdt": funding_usdt,
"current_capital": current_capital, "current_capital": current_capital,
"show_perp_funds": _show_perp_funds, "show_perp_funds": _show_perp_funds,
"exchange_mode_label": _snapshot_mode_label,
"options_funding_usdc": options_funding_usdc, "options_funding_usdc": options_funding_usdc,
"options_funding_usdt": options_funding_usdt, "options_funding_usdt": options_funding_usdt,
"options_trading_usdc": options_trading_usdc, "options_trading_usdc": options_trading_usdc,
+39 -6
View File
@@ -386,20 +386,42 @@
} }
function bindTradeModeAutoRefresh(body) { function bindTradeModeAutoRefresh(body) {
const modeSel = body.querySelector('.env-field-input[data-env-key="OKX_TRADE_MODE"]'); bindEnvSelectAutoRefresh(
if (!modeSel || modeSel.dataset.modeRefreshBound === "1") return; body,
modeSel.dataset.modeRefreshBound = "1"; "OKX_TRADE_MODE",
"切换交易模式并刷新配置…",
"交易模式已切换为当前选项,配置区已刷新",
"modeRefreshBound",
true
);
bindEnvSelectAutoRefresh(
body,
"SIM_DEFAULT_MODE",
"切换撮合模式并刷新配置…",
"撮合模式已切换,配置区已刷新(sim 隐藏 API / live 显示实盘项)",
"simModeRefreshBound",
false
);
}
function bindEnvSelectAutoRefresh(body, key, pendingMsg, okMsg, flag, jumpModeSection) {
const modeSel = body && body.querySelector('.env-field-input[data-env-key="' + key + '"]');
if (!modeSel || modeSel.dataset[flag] === "1") return;
modeSel.dataset[flag] = "1";
modeSel.addEventListener("change", async () => { modeSel.addEventListener("change", async () => {
const status = document.getElementById("env-config-status"); const status = document.getElementById("env-config-status");
const nextMode = modeSel.value; const nextMode = modeSel.value;
setStatus(status, "切换交易模式并刷新配置…"); setStatus(status, pendingMsg);
try { try {
const payload = { values: {} };
payload.values[key] = nextMode;
await fetchJson("/api/settings/env", { await fetchJson("/api/settings/env", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ values: { OKX_TRADE_MODE: nextMode } }), body: JSON.stringify(payload),
}); });
await loadEnvConfig(true); await loadEnvConfig(true);
if (jumpModeSection) {
const page = envConfigRoot() || document.querySelector(".env-config-page"); const page = envConfigRoot() || document.querySelector(".env-config-page");
const newBody = page && page.querySelector("#env-config-body"); const newBody = page && page.querySelector("#env-config-body");
const idx = newBody && newBody.dataset.envModeSectionIdx; const idx = newBody && newBody.dataset.envModeSectionIdx;
@@ -407,7 +429,18 @@
const radio = document.getElementById("env-sec-" + idx); const radio = document.getElementById("env-sec-" + idx);
if (radio) radio.checked = true; if (radio) radio.checked = true;
} }
setStatus(status, "交易模式已切换为当前选项,配置区已刷新"); }
setStatus(status, okMsg);
if (typeof global.refreshAccountSnapshot === "function") {
global.refreshAccountSnapshot({ force: true, silent: true });
}
// 顶栏交易所名:okx_sim / okx_live
const label = nextMode === "sim" ? "okx_sim" : nextMode === "live" ? "okx_live" : "";
if (label) {
document.querySelectorAll('[data-funds-field="exchange-mode-label"]').forEach((el) => {
el.textContent = label;
});
}
} catch (e) { } catch (e) {
setStatus(status, e.message || "切换失败", true); setStatus(status, e.message || "切换失败", true);
} }
+3
View File
@@ -100,6 +100,9 @@ HOT_RELOAD_EXACT = frozenset({
"OKX_OPTIONS_BUDGET_BUFFER", "OKX_OPTIONS_BUDGET_BUFFER",
"OKX_TRADE_MODE", "OKX_TRADE_MODE",
"SIM_DEFAULT_MODE", "SIM_DEFAULT_MODE",
"SIM_INITIAL_EQUITY_USDT",
"SIM_INITIAL_USDC",
"SIM_FEE_RATE",
"MAX_ACTIVE_HEDGE_PLANS", "MAX_ACTIVE_HEDGE_PLANS",
"HEDGE_PLAN_LIVE_ORDER", "HEDGE_PLAN_LIVE_ORDER",
"HEDGE_PLAN_OPTION_PRIMARY", "HEDGE_PLAN_OPTION_PRIMARY",
+68 -3
View File
@@ -17,12 +17,42 @@ from lib.env.env_schema import (
) )
# 各所「交易所与实盘」字段(顺序即页面顺序) # 各所「交易所与实盘」字段(顺序即页面顺序)
_OKX_LIVE_ONLY_KEYS = frozenset(
{
"LIVE_TRADING_ENABLED",
"OKX_API_KEY",
"OKX_API_SECRET",
"OKX_API_PASSPHRASE",
}
)
_SIM_FUNDS_SECTION: dict[str, Any] = {
"title": "模拟资金",
"fields": [
(
"SIM_INITIAL_EQUITY_USDT",
"初始权益 USDT",
"重置模拟钱包时写入资金账户 USDT;改完需在系统设置「模拟资金」点重置才生效",
),
(
"SIM_INITIAL_USDC",
"初始 USDC",
"重置时写入期权侧 USDC;改完需重置才生效",
),
(
"SIM_FEE_RATE",
"模拟手续费率",
"如 0.0005=万五;撮合立即按此费率扣费",
),
],
}
_EXCHANGE_LIVE_FIELDS: dict[str, list[tuple[str, str, str]]] = { _EXCHANGE_LIVE_FIELDS: dict[str, list[tuple[str, str, str]]] = {
"okx": [ "okx": [
( (
"SIM_DEFAULT_MODE", "SIM_DEFAULT_MODE",
"撮合模式", "撮合模式",
"sim=本地模拟资金(公开行情撮合,不下真实单); live=实盘账户.保存后立即切换", "sim=本地模拟资金; live=实盘.保存后立即切换;sim 下隐藏 API/实盘开关",
), ),
("LIVE_TRADING_ENABLED", "开启实盘下单", "仅 live 模式下生效;关闭时即使 live 也不向交易所发单"), ("LIVE_TRADING_ENABLED", "开启实盘下单", "仅 live 模式下生效;关闭时即使 live 也不向交易所发单"),
("OKX_API_KEY", "API Key", "账户 API(永续+期权共用)"), ("OKX_API_KEY", "API Key", "账户 API(永续+期权共用)"),
@@ -372,15 +402,41 @@ def _hedge_fields_for_mode(mode: str) -> list[tuple[str, str, str]]:
return [] return []
def _trading_mode_for_env_ui() -> str:
try:
from lib.sim.mode_lib import peek_persisted_trading_mode, default_trading_mode
return peek_persisted_trading_mode() or default_trading_mode()
except Exception:
return "sim"
def _okx_exchange_fields_for_trading_mode(trading_mode: str) -> list[tuple[str, str, str]]:
fields = list(_EXCHANGE_LIVE_FIELDS["okx"])
tm = (trading_mode or "").strip().lower()
if tm == "sim":
return [f for f in fields if f[0] not in _OKX_LIVE_ONLY_KEYS]
return fields
def ui_sections_for_exchange( def ui_sections_for_exchange(
exchange_key: str, exchange_key: str,
*, *,
mode: str | None = None, mode: str | None = None,
trading_mode: str | None = None,
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
ex = (exchange_key or "").strip().lower() ex = (exchange_key or "").strip().lower()
sections: list[dict[str, Any]] = [] sections: list[dict[str, Any]] = []
tm = (trading_mode or "").strip().lower()
if not tm:
tm = _trading_mode_for_env_ui() if ex == "okx" else "live"
if ex == "okx":
live_fields = _okx_exchange_fields_for_trading_mode(tm)
else:
live_fields = _EXCHANGE_LIVE_FIELDS.get(ex, _EXCHANGE_LIVE_FIELDS["okx"]) live_fields = _EXCHANGE_LIVE_FIELDS.get(ex, _EXCHANGE_LIVE_FIELDS["okx"])
sections.append({"title": "交易所与实盘", "fields": live_fields}) sections.append({"title": "交易所与实盘", "fields": live_fields})
if ex == "okx" and tm == "sim":
sections.append(_SIM_FUNDS_SECTION)
sections.extend(_SHARED_SECTIONS) sections.extend(_SHARED_SECTIONS)
if ex in _MODE_SECTION.get("exchanges", frozenset()): if ex in _MODE_SECTION.get("exchanges", frozenset()):
from lib.hedge_plan.okx_trade_mode_lib import normalize_okx_trade_mode from lib.hedge_plan.okx_trade_mode_lib import normalize_okx_trade_mode
@@ -406,7 +462,11 @@ def ui_allowed_keys(exchange_key: str) -> frozenset[str]:
ex = (exchange_key or "").strip().lower() ex = (exchange_key or "").strip().lower()
if ex == "okx": if ex == "okx":
keys.add("OKX_TRADE_MODE") keys.add("OKX_TRADE_MODE")
# 允许写入遗留键,避免旧自动化/手改失败;页面不再展示 keys.add("SIM_DEFAULT_MODE")
# 切模式后同请求可能带上对侧字段,始终放行
keys.update(_OKX_LIVE_ONLY_KEYS)
for item in _SIM_FUNDS_SECTION["fields"]:
keys.add(item[0])
for item in _HEDGE_PLAN_SECTION["fields"]: for item in _HEDGE_PLAN_SECTION["fields"]:
keys.add(item[0]) keys.add(item[0])
for item in _OPTIONS_SECTION["fields"]: for item in _OPTIONS_SECTION["fields"]:
@@ -422,9 +482,14 @@ def build_env_ui_payload(
schema = _schema_field_map(example_path) schema = _schema_field_map(example_path)
env_lines = read_env_lines(env_path) env_lines = read_env_lines(env_path)
values = env_get_all(env_lines) values = env_get_all(env_lines)
trading_mode = ""
if (exchange_key or "").strip().lower() == "okx":
trading_mode = _effective_env_value("SIM_DEFAULT_MODE", values, "sim") or _trading_mode_for_env_ui()
groups: list[dict[str, Any]] = [] groups: list[dict[str, Any]] = []
for sec in ui_sections_for_exchange( for sec in ui_sections_for_exchange(
exchange_key, mode=values.get("OKX_TRADE_MODE") or "" exchange_key,
mode=values.get("OKX_TRADE_MODE") or "",
trading_mode=trading_mode,
): ):
fields = [ fields = [
_build_field(key, label, note, schema, values) _build_field(key, label, note, schema, values)
@@ -1178,6 +1178,9 @@ function applyAccountSnapshot(data){
if(typeof data.show_perp_funds !== "undefined"){ if(typeof data.show_perp_funds !== "undefined"){
applyPerpFundsVisibility(data.show_perp_funds); applyPerpFundsVisibility(data.show_perp_funds);
} }
if(data.exchange_mode_label){
setFundsFieldText("exchange-mode-label", data.exchange_mode_label);
}
if(data.funding_usdt != null && data.funding_usdt !== ""){ if(data.funding_usdt != null && data.funding_usdt !== ""){
setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`); setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`);
} }
+1 -1
View File
@@ -144,7 +144,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
<script> <script>
window.__INSTANCE_DISPLAY__ = {{ display | tojson }}; window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
</script> </script>
<script src="/static/instance_settings_prefs.js?v=21"></script> <script src="/static/instance_settings_prefs.js?v=22"></script>
<script src="/static/instance_live.js?v=7"></script> <script src="/static/instance_live.js?v=7"></script>
<script src="/static/instance_embed.js?v=31"></script> <script src="/static/instance_embed.js?v=31"></script>
<script src="/static/instance_mobile_nav.js?v=2"></script> <script src="/static/instance_mobile_nav.js?v=2"></script>
+4 -1
View File
@@ -1444,6 +1444,9 @@ function applyAccountSnapshot(data){
if(typeof data.show_perp_funds !== "undefined"){ if(typeof data.show_perp_funds !== "undefined"){
applyPerpFundsVisibility(data.show_perp_funds); applyPerpFundsVisibility(data.show_perp_funds);
} }
if(data.exchange_mode_label){
setFundsFieldText("exchange-mode-label", data.exchange_mode_label);
}
if(data.funding_usdt != null && data.funding_usdt !== ""){ if(data.funding_usdt != null && data.funding_usdt !== ""){
setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`); setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`);
} }
@@ -1829,6 +1832,6 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
{% endif %} {% endif %}
</script> </script>
<script src="/static/instance_settings_prefs.js?v=21"></script> <script src="/static/instance_settings_prefs.js?v=22"></script>
</body> </body>
</html> </html>
@@ -2,7 +2,7 @@
<div class="instance-header-stats{% if options_enabled %} instance-header-stats--options{% endif %}"> <div class="instance-header-stats{% if options_enabled %} instance-header-stats--options{% endif %}">
<div class="stat-strip-item stat-strip-item--primary"> <div class="stat-strip-item stat-strip-item--primary">
<div class="label">交易所</div> <div class="label">交易所</div>
<div class="value">{{ exchange_display }}{% if is_sim_mode|default(false) %} <span class="sim-mode-badge" title="本地模拟资金撮合,不下真实交易所单">模拟</span>{% endif %}</div> <div class="value" id="exchange-mode-label" data-funds-field="exchange-mode-label">{{ exchange_display }}</div>
</div> </div>
<div class="stat-strip-item"> <div class="stat-strip-item">
<div class="label">交易日</div> <div class="label">交易日</div>
+8
View File
@@ -78,3 +78,11 @@ def set_trading_mode(get_db: Callable, mode: str) -> str:
def is_sim_mode(get_db: Callable) -> bool: def is_sim_mode(get_db: Callable) -> bool:
return get_trading_mode(get_db) == MODE_SIM return get_trading_mode(get_db) == MODE_SIM
def exchange_mode_label(*, is_sim: bool | None = None, get_db: Callable | None = None, exchange_key: str = "okx") -> str:
"""顶栏交易所展示: okx_sim / okx_live."""
ex = (exchange_key or "okx").strip().lower() or "okx"
if is_sim is None and callable(get_db):
is_sim = is_sim_mode(get_db)
return f"{ex}_sim" if bool(is_sim) else f"{ex}_live"