Expose sim/live matching mode in env config UI.
Saving SIM_DEFAULT_MODE also switches runtime trading.mode immediately. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-2
@@ -72,8 +72,8 @@ TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
|
|||||||
LIVE_TRADING_ENABLED=true
|
LIVE_TRADING_ENABLED=true
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 模拟资金(本地 SQLite 钱包; 系统设置可切换 模拟资金/实盘)
|
# 模拟资金(本地 SQLite 钱包; env「撮合模式」或系统设置可切换 sim/live)
|
||||||
# 交易模式默认由系统设置(runtime trading.mode)切换; 也可启动时指定默认
|
# sim=本地模拟资金撮合; live=实盘账户
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
SIM_DEFAULT_MODE=sim
|
SIM_DEFAULT_MODE=sim
|
||||||
SIM_INITIAL_EQUITY_USDT=10000
|
SIM_INITIAL_EQUITY_USDT=10000
|
||||||
|
|||||||
Vendored
+5
@@ -99,6 +99,7 @@ HOT_RELOAD_EXACT = frozenset({
|
|||||||
"OKX_OPTIONS_TRADE_BUDGET_USDC",
|
"OKX_OPTIONS_TRADE_BUDGET_USDC",
|
||||||
"OKX_OPTIONS_BUDGET_BUFFER",
|
"OKX_OPTIONS_BUDGET_BUFFER",
|
||||||
"OKX_TRADE_MODE",
|
"OKX_TRADE_MODE",
|
||||||
|
"SIM_DEFAULT_MODE",
|
||||||
"MAX_ACTIVE_HEDGE_PLANS",
|
"MAX_ACTIVE_HEDGE_PLANS",
|
||||||
"HEDGE_PLAN_LIVE_ORDER",
|
"HEDGE_PLAN_LIVE_ORDER",
|
||||||
"HEDGE_PLAN_OPTION_PRIMARY",
|
"HEDGE_PLAN_OPTION_PRIMARY",
|
||||||
@@ -159,6 +160,10 @@ SELECT_OPTIONS: dict[str, tuple[tuple[str, str], ...]] = {
|
|||||||
("perp_options", "永期对冲"),
|
("perp_options", "永期对冲"),
|
||||||
("options_options", "期期对冲"),
|
("options_options", "期期对冲"),
|
||||||
),
|
),
|
||||||
|
"SIM_DEFAULT_MODE": (
|
||||||
|
("sim", "模拟(sim)"),
|
||||||
|
("live", "实盘(live)"),
|
||||||
|
),
|
||||||
"HEDGE_PLAN_OPTION_PRIMARY": (
|
"HEDGE_PLAN_OPTION_PRIMARY": (
|
||||||
("true", "以期权为主"),
|
("true", "以期权为主"),
|
||||||
("false", "保险模式"),
|
("false", "保险模式"),
|
||||||
|
|||||||
Vendored
+17
-1
@@ -19,7 +19,12 @@ from lib.env.env_schema import (
|
|||||||
# 各所「交易所与实盘」字段(顺序即页面顺序)
|
# 各所「交易所与实盘」字段(顺序即页面顺序)
|
||||||
_EXCHANGE_LIVE_FIELDS: dict[str, list[tuple[str, str, str]]] = {
|
_EXCHANGE_LIVE_FIELDS: dict[str, list[tuple[str, str, str]]] = {
|
||||||
"okx": [
|
"okx": [
|
||||||
("LIVE_TRADING_ENABLED", "开启实盘下单", "关闭时仅走本地流程,不向交易所发单"),
|
(
|
||||||
|
"SIM_DEFAULT_MODE",
|
||||||
|
"撮合模式",
|
||||||
|
"sim=本地模拟资金(公开行情撮合,不下真实单); live=实盘账户.保存后立即切换",
|
||||||
|
),
|
||||||
|
("LIVE_TRADING_ENABLED", "开启实盘下单", "仅 live 模式下生效;关闭时即使 live 也不向交易所发单"),
|
||||||
("OKX_API_KEY", "API Key", "账户 API(永续+期权共用)"),
|
("OKX_API_KEY", "API Key", "账户 API(永续+期权共用)"),
|
||||||
("OKX_API_SECRET", "API Secret", "账户 API(永续+期权共用)"),
|
("OKX_API_SECRET", "API Secret", "账户 API(永续+期权共用)"),
|
||||||
("OKX_API_PASSPHRASE", "API Passphrase", "OKX 必填"),
|
("OKX_API_PASSPHRASE", "API Passphrase", "OKX 必填"),
|
||||||
@@ -291,6 +296,7 @@ _RUNTIME_ENV_DEFAULTS: dict[str, str] = {
|
|||||||
"OKX_OPTIONS_MAX_DTE_DAYS": "2",
|
"OKX_OPTIONS_MAX_DTE_DAYS": "2",
|
||||||
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS": "0",
|
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS": "0",
|
||||||
"OKX_TRADE_MODE": "options",
|
"OKX_TRADE_MODE": "options",
|
||||||
|
"SIM_DEFAULT_MODE": "sim",
|
||||||
"MAX_ACTIVE_HEDGE_PLANS": "1",
|
"MAX_ACTIVE_HEDGE_PLANS": "1",
|
||||||
"HEDGE_PLAN_OO_CLOSE_MODE_ENABLED": "true",
|
"HEDGE_PLAN_OO_CLOSE_MODE_ENABLED": "true",
|
||||||
"HEDGE_PLAN_OO_BIAS_SPLIT_BY": "budget",
|
"HEDGE_PLAN_OO_BIAS_SPLIT_BY": "budget",
|
||||||
@@ -303,6 +309,16 @@ _RUNTIME_ENV_DEFAULTS: dict[str, str] = {
|
|||||||
|
|
||||||
|
|
||||||
def _effective_env_value(key: str, file_values: dict[str, str], schema_default: str = "") -> str:
|
def _effective_env_value(key: str, file_values: dict[str, str], schema_default: str = "") -> str:
|
||||||
|
if key == "SIM_DEFAULT_MODE":
|
||||||
|
# 展示当前生效撮合模式(运行时 trading.mode 优先)
|
||||||
|
try:
|
||||||
|
from lib.sim.mode_lib import peek_persisted_trading_mode
|
||||||
|
|
||||||
|
cur = peek_persisted_trading_mode()
|
||||||
|
if cur:
|
||||||
|
return cur
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
if key == "OKX_TRADE_MODE":
|
if key == "OKX_TRADE_MODE":
|
||||||
# 展示值必须与运行时 get_okx_trade_mode() 一致,避免未写入时默认 options 静默改模式
|
# 展示值必须与运行时 get_okx_trade_mode() 一致,避免未写入时默认 options 静默改模式
|
||||||
file_val = str(file_values.get(key) or "").strip() if key in file_values else ""
|
file_val = str(file_values.get(key) or "").strip() if key in file_values else ""
|
||||||
|
|||||||
@@ -51,12 +51,26 @@ def apply_env_reload(env_path: str, get_db: Callable, changed_keys: list[str], g
|
|||||||
field_map[field["key"]] = field
|
field_map[field["key"]] = field
|
||||||
for key in changed_keys:
|
for key in changed_keys:
|
||||||
meta = field_map.get(key) or {}
|
meta = field_map.get(key) or {}
|
||||||
if meta.get("hot_reload") and not meta.get("restart_required"):
|
# SIM_DEFAULT_MODE 不在 example schema 的 hot 列表时仍应热切
|
||||||
|
is_hot = bool(meta.get("hot_reload") and not meta.get("restart_required"))
|
||||||
|
if key == "SIM_DEFAULT_MODE":
|
||||||
|
is_hot = True
|
||||||
|
if is_hot:
|
||||||
val = os.getenv(key)
|
val = os.getenv(key)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
hot[key] = val
|
hot[key] = val
|
||||||
if hot:
|
if hot:
|
||||||
set_config_overrides(get_db, hot)
|
set_config_overrides(get_db, hot)
|
||||||
|
# 撮合模式:写入 .env 的同时切换运行时 trading.mode(sim|live)
|
||||||
|
if "SIM_DEFAULT_MODE" in changed_keys:
|
||||||
|
try:
|
||||||
|
from lib.sim.mode_lib import set_trading_mode
|
||||||
|
|
||||||
|
raw = (os.getenv("SIM_DEFAULT_MODE") or "").strip().lower()
|
||||||
|
if raw in ("sim", "live"):
|
||||||
|
set_trading_mode(get_db, raw)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
from lib.env.env_schema import updates_need_restart
|
from lib.env.env_schema import updates_need_restart
|
||||||
|
|
||||||
return {"restart_required": updates_need_restart(groups, changed_keys)}
|
return {"restart_required": updates_need_restart(groups, changed_keys)}
|
||||||
|
|||||||
@@ -20,6 +20,34 @@ def default_trading_mode() -> str:
|
|||||||
return MODE_SIM
|
return MODE_SIM
|
||||||
|
|
||||||
|
|
||||||
|
def peek_persisted_trading_mode(db_path: str | None = None) -> str | None:
|
||||||
|
"""只读 SQLite 中的 trading.mode(供 env 配置页展示当前生效值)."""
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
path = (db_path or os.getenv("DB_PATH") or "crypto.db").strip()
|
||||||
|
if not path:
|
||||||
|
return None
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(path, timeout=2)
|
||||||
|
try:
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT value FROM app_runtime_settings WHERE key=?",
|
||||||
|
(TRADING_MODE_KEY,),
|
||||||
|
).fetchone()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
m = str(row[0] or "").strip().lower()
|
||||||
|
return m if m in VALID_MODES else None
|
||||||
|
|
||||||
|
|
||||||
def normalize_mode(mode: str | None) -> str:
|
def normalize_mode(mode: str | None) -> str:
|
||||||
m = (mode or "").strip().lower()
|
m = (mode or "").strip().lower()
|
||||||
if m in VALID_MODES:
|
if m in VALID_MODES:
|
||||||
|
|||||||
Reference in New Issue
Block a user