diff --git a/app.py b/app.py
index 65ad1b5..440461c 100644
--- a/app.py
+++ b/app.py
@@ -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,
diff --git a/lib/common/static/instance_settings_prefs.js b/lib/common/static/instance_settings_prefs.js
index 4565900..1d93e73 100644
--- a/lib/common/static/instance_settings_prefs.js
+++ b/lib/common/static/instance_settings_prefs.js
@@ -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);
}
diff --git a/lib/env/env_schema.py b/lib/env/env_schema.py
index 88f0150..bd4e47e 100644
--- a/lib/env/env_schema.py
+++ b/lib/env/env_schema.py
@@ -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",
diff --git a/lib/env/env_ui_manifest.py b/lib/env/env_ui_manifest.py
index 37555ca..7cac240 100644
--- a/lib/env/env_ui_manifest.py
+++ b/lib/env/env_ui_manifest.py
@@ -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)
diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html
index ac79c2e..d38e3b5 100644
--- a/lib/instance/templates/embed_boot_scripts.html
+++ b/lib/instance/templates/embed_boot_scripts.html
@@ -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`);
}
diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html
index ed268c1..b1f4527 100644
--- a/lib/instance/templates/embed_shell.html
+++ b/lib/instance/templates/embed_shell.html
@@ -144,7 +144,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
-
+
diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html
index 7cca29a..f353bea 100644
--- a/lib/instance/templates/index.html
+++ b/lib/instance/templates/index.html
@@ -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 %}
-
+