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_trading_usdt = None
_sim_mode_for_header = False
_exchange_display_for_header = EXCHANGE_DISPLAY_NAME
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
_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:
_sim_mode_for_header = False
_exchange_display_for_header = EXCHANGE_DISPLAY_NAME
if (
OKX_OPTIONS_ENABLED
and embed_mode != "fragment"
@@ -5258,7 +5265,7 @@ def render_main_page(page="options", embed_mode=None):
key_rule_ctx=key_rule_ctx,
funds_fmt=format_funds_u,
options_funding_label=options_funding_label,
exchange_display=EXCHANGE_DISPLAY_NAME,
exchange_display=_exchange_display_for_header,
options_enabled=OKX_OPTIONS_ENABLED,
trading_mode=("sim" if _sim_mode_for_header else "live"),
is_sim_mode=_sim_mode_for_header,
@@ -5532,10 +5539,18 @@ def api_account_snapshot():
except Exception:
options_unrealized_pnl = None
_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({
"funding_usdt": funding_usdt,
"current_capital": current_capital,
"show_perp_funds": _show_perp_funds,
"exchange_mode_label": _snapshot_mode_label,
"options_funding_usdc": options_funding_usdc,
"options_funding_usdt": options_funding_usdt,
"options_trading_usdc": options_trading_usdc,
+45 -12
View File
@@ -386,28 +386,61 @@
}
function bindTradeModeAutoRefresh(body) {
const modeSel = body.querySelector('.env-field-input[data-env-key="OKX_TRADE_MODE"]');
if (!modeSel || modeSel.dataset.modeRefreshBound === "1") return;
modeSel.dataset.modeRefreshBound = "1";
bindEnvSelectAutoRefresh(
body,
"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 () => {
const status = document.getElementById("env-config-status");
const nextMode = modeSel.value;
setStatus(status, "切换交易模式并刷新配置…");
setStatus(status, pendingMsg);
try {
const payload = { values: {} };
payload.values[key] = nextMode;
await fetchJson("/api/settings/env", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ values: { OKX_TRADE_MODE: nextMode } }),
body: JSON.stringify(payload),
});
await loadEnvConfig(true);
const page = envConfigRoot() || document.querySelector(".env-config-page");
const newBody = page && page.querySelector("#env-config-body");
const idx = newBody && newBody.dataset.envModeSectionIdx;
if (idx != null) {
const radio = document.getElementById("env-sec-" + idx);
if (radio) radio.checked = true;
if (jumpModeSection) {
const page = envConfigRoot() || document.querySelector(".env-config-page");
const newBody = page && page.querySelector("#env-config-body");
const idx = newBody && newBody.dataset.envModeSectionIdx;
if (idx != null) {
const radio = document.getElementById("env-sec-" + idx);
if (radio) radio.checked = true;
}
}
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;
});
}
setStatus(status, "交易模式已切换为当前选项,配置区已刷新");
} catch (e) {
setStatus(status, e.message || "切换失败", true);
}
+3
View File
@@ -100,6 +100,9 @@ HOT_RELOAD_EXACT = frozenset({
"OKX_OPTIONS_BUDGET_BUFFER",
"OKX_TRADE_MODE",
"SIM_DEFAULT_MODE",
"SIM_INITIAL_EQUITY_USDT",
"SIM_INITIAL_USDC",
"SIM_FEE_RATE",
"MAX_ACTIVE_HEDGE_PLANS",
"HEDGE_PLAN_LIVE_ORDER",
"HEDGE_PLAN_OPTION_PRIMARY",
+69 -4
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]]] = {
"okx": [
(
"SIM_DEFAULT_MODE",
"撮合模式",
"sim=本地模拟资金(公开行情撮合,不下真实单); live=实盘账户.保存后立即切换",
"sim=本地模拟资金; live=实盘.保存后立即切换;sim 下隐藏 API/实盘开关",
),
("LIVE_TRADING_ENABLED", "开启实盘下单", "仅 live 模式下生效;关闭时即使 live 也不向交易所发单"),
("OKX_API_KEY", "API Key", "账户 API(永续+期权共用)"),
@@ -372,15 +402,41 @@ def _hedge_fields_for_mode(mode: str) -> list[tuple[str, str, str]]:
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(
exchange_key: str,
*,
mode: str | None = None,
trading_mode: str | None = None,
) -> list[dict[str, Any]]:
ex = (exchange_key or "").strip().lower()
sections: list[dict[str, Any]] = []
live_fields = _EXCHANGE_LIVE_FIELDS.get(ex, _EXCHANGE_LIVE_FIELDS["okx"])
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"])
sections.append({"title": "交易所与实盘", "fields": live_fields})
if ex == "okx" and tm == "sim":
sections.append(_SIM_FUNDS_SECTION)
sections.extend(_SHARED_SECTIONS)
if ex in _MODE_SECTION.get("exchanges", frozenset()):
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()
if ex == "okx":
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"]:
keys.add(item[0])
for item in _OPTIONS_SECTION["fields"]:
@@ -422,9 +482,14 @@ def build_env_ui_payload(
schema = _schema_field_map(example_path)
env_lines = read_env_lines(env_path)
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]] = []
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 = [
_build_field(key, label, note, schema, values)
@@ -1178,6 +1178,9 @@ function applyAccountSnapshot(data){
if(typeof data.show_perp_funds !== "undefined"){
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 !== ""){
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>
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
</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_embed.js?v=31"></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"){
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 !== ""){
setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`);
}
@@ -1829,6 +1832,6 @@ document.addEventListener("DOMContentLoaded", function () {
});
{% endif %}
</script>
<script src="/static/instance_settings_prefs.js?v=21"></script>
<script src="/static/instance_settings_prefs.js?v=22"></script>
</body>
</html>
@@ -2,7 +2,7 @@
<div class="instance-header-stats{% if options_enabled %} instance-header-stats--options{% endif %}">
<div class="stat-strip-item stat-strip-item--primary">
<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 class="stat-strip-item">
<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:
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"