diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 0afd199..2619aae 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -6394,6 +6394,10 @@ def strategy_roll_page(): return redirect("/strategy") +# 根目录 strategy_* 与币安/Gate 共用同一套属性名(OKX 内部仍用 normalize_okx_symbol / ensure_okx_live_ready) +normalize_exchange_symbol = normalize_okx_symbol +ensure_exchange_live_ready = ensure_okx_live_ready + from strategy_register import install_strategy_trading from strategy_trend_register import install_strategy_trend diff --git a/manual_trading_hub/.env.example b/manual_trading_hub/.env.example index 80a17fa..642ed7c 100644 --- a/manual_trading_hub/.env.example +++ b/manual_trading_hub/.env.example @@ -14,8 +14,8 @@ HUB_PORT=5100 # HUB_BRIDGE_TOKEN=your-long-random-token # 逗号分隔的账户 id,强制关闭(不参与监控/全局全平;设置页对应行勾选框灰掉) -# 默认 1 = OKX;不用 OKX 可保持;要用 OKX 请删掉本行或改为空 -HUB_DISABLED_IDS=1 +# 留空 = 不强制关闭;仅不想用 OKX 时可设 HUB_DISABLED_IDS=1 +HUB_DISABLED_IDS= # true=允许 RFC1918 私网访问中控页面;false=仅 127.0.0.1 HUB_TRUST_LAN=true diff --git a/manual_trading_hub/env_load.py b/manual_trading_hub/env_load.py new file mode 100644 index 0000000..7979200 --- /dev/null +++ b/manual_trading_hub/env_load.py @@ -0,0 +1,34 @@ +"""加载 manual_trading_hub/.env(Windows 直接 python hub.py 时也需要)。""" +from __future__ import annotations + +import os +from pathlib import Path + +HUB_DIR = Path(__file__).resolve().parent + + +def load_hub_dotenv() -> None: + path = HUB_DIR / ".env" + if not path.is_file(): + return + raw_bytes = path.read_bytes() + text = "" + for enc in ("utf-8-sig", "utf-16", "utf-16-le", "utf-16-be"): + try: + text = raw_bytes.decode(enc) + break + except Exception: + continue + if not text: + text = raw_bytes.decode("utf-8", errors="ignore") + text = text.replace("\x00", "") + for line in text.splitlines(): + raw = line.strip() + if not raw or raw.startswith("#") or "=" not in raw: + continue + key, value = raw.split("=", 1) + clean_key = key.strip().lstrip("\ufeff") + if not clean_key.replace("_", "").isalnum(): + continue + clean_value = value.strip().strip('"').strip("'") + os.environ[clean_key] = clean_value diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py index bc73f47..a47cfea 100644 --- a/manual_trading_hub/hub.py +++ b/manual_trading_hub/hub.py @@ -8,6 +8,10 @@ import asyncio import os from pathlib import Path +from env_load import load_hub_dotenv + +load_hub_dotenv() + import httpx from fastapi import Body, FastAPI, HTTPException, Request from fastapi.responses import FileResponse, JSONResponse @@ -39,7 +43,7 @@ HUB_BRIDGE_TOKEN = (os.getenv("HUB_BRIDGE_TOKEN") or os.getenv("CONTROL_TOKEN") _trust_raw = (os.getenv("HUB_TRUST_LAN", "true") or "").strip().lower() HUB_TRUST_LAN = _trust_raw not in ("0", "false", "no", "off") DIR = Path(__file__).resolve().parent -HUB_BUILD = "20260521-no-trade-ui" +HUB_BUILD = "20260522-settings-fix" def _is_local(host: str | None) -> bool: @@ -224,9 +228,17 @@ class SettingsBody(BaseModel): @app.post("/api/settings") def api_save_settings(body: SettingsBody): - data = {"version": 1, "exchanges": body.exchanges} - save_settings(data) - return {"ok": True} + force_off = env_force_disabled_ids() + to_save = [] + for ex in body.exchanges: + row = dict(ex) + eid = str(row.get("id", "")).strip() + if eid in force_off: + row["enabled"] = False + row.pop("env_disabled", None) + to_save.append(row) + save_settings({"version": 1, "exchanges": to_save}) + return {"ok": True, "settings": load_settings()} @app.get("/api/settings/meta") @@ -438,6 +450,8 @@ def api_ping(): "trade_ui": False, "features": ["monitor", "settings", "auth"], "password_required": password_required(), + "env_disabled_ids": sorted(env_force_disabled_ids()), + "hub_disabled_ids_raw": (os.getenv("HUB_DISABLED_IDS") or ""), } diff --git a/manual_trading_hub/settings_store.py b/manual_trading_hub/settings_store.py index a124808..1bd8502 100644 --- a/manual_trading_hub/settings_store.py +++ b/manual_trading_hub/settings_store.py @@ -27,7 +27,7 @@ DEFAULT_EXCHANGES = [ "agent_url": "http://127.0.0.1:15201", "flask_url": "http://127.0.0.1:5004", "review_url": "http://127.0.0.1:5004/records", - "enabled": False, + "enabled": True, "capabilities": ["key", "trend"], }, { @@ -60,7 +60,8 @@ def _ids_from_csv(raw: str | None) -> set[str]: def env_force_disabled_ids() -> set[str]: - raw = os.getenv("HUB_DISABLED_IDS", "1").strip() + # 未设置时默认不强制关闭任何账户;要用旧行为可设 HUB_DISABLED_IDS=1 + raw = (os.getenv("HUB_DISABLED_IDS") or "").strip() return _ids_from_csv(raw) diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 549ceab..c7f68bc 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -270,8 +270,11 @@ else parts.push("中控未设 HUB_BRIDGE_TOKEN(实例需 APP_AUTH_DISABLED 或同令牌)"); if (m.public_origin) parts.push("浏览器外链基址: " + m.public_origin); else parts.push("未设 HUB_PUBLIC_ORIGIN(复盘链接仅本机可开)"); - if ((m.env_disabled_ids || []).length) - parts.push("环境强制关闭 id: " + m.env_disabled_ids.join(", ")); + if ((m.env_disabled_ids || []).length) { + parts.push("环境强制关闭 id: " + m.env_disabled_ids.join(", ") + "(改 .env 后须重启 hub)"); + } else { + parts.push("HUB_DISABLED_IDS 未强制关闭任何账户"); + } el.textContent = parts.join(" · "); } catch (_) {} } @@ -361,7 +364,13 @@ const j = await r.json(); if (j.ok) { showToast("设置已保存(已写入 hub_settings.json)"); - await loadSettingsUI(); + if (j.settings) { + settingsCache = j.settings; + renderSettingsList(j.settings); + loadSettingsMetaLine(); + } else { + await loadSettingsUI(); + } } else showToast("保存失败", true); } catch (e) { showToast(String(e), true); @@ -395,10 +404,12 @@ }); settingsCache = data; renderSettingsList(data); + showToast("已添加一行,请填写 URL 后点「保存设置」"); }; initAuth().then((ok) => { if (!ok) return; + loadSettings().catch(() => {}); setActiveNav(); window.addEventListener("popstate", setActiveNav); }); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index a3d153d..575ca4a 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -7,7 +7,7 @@ - + @@ -80,6 +80,6 @@
- +