diff --git a/ctp_premarket_connect.py b/ctp_premarket_connect.py index 1f015de..c56b644 100644 --- a/ctp_premarket_connect.py +++ b/ctp_premarket_connect.py @@ -12,7 +12,6 @@ import threading import time from typing import Callable -from ctp_settings import is_ctp_auto_connect_enabled from market_sessions import in_premarket_connect_window, is_trading_session from vnpy_bridge import ctp_start_connect, ctp_status @@ -60,10 +59,7 @@ def start_ctp_premarket_connect_worker( while True: sleep_sec = max(30, interval) try: - gs = get_setting_fn - if gs is None: - from fee_specs import get_setting as gs - if is_ctp_auto_connect_enabled(gs) and should_auto_connect_now(): + if should_auto_connect_now(): mode = get_mode_fn() st = ctp_status(mode) if ( @@ -71,7 +67,7 @@ def start_ctp_premarket_connect_worker( and not st.get("connecting") and int(st.get("login_cooldown_sec") or 0) <= 0 ): - info = ctp_start_connect(mode, force=False) + info = ctp_start_connect(mode, force=False, scheduled=True) if info.get("started"): if is_trading_session(): logger.info("交易时段内自动连接 CTP [%s]", mode) diff --git a/ctp_reconnect.py b/ctp_reconnect.py index 7f8683d..47581f5 100644 --- a/ctp_reconnect.py +++ b/ctp_reconnect.py @@ -13,7 +13,6 @@ import time from typing import Callable from ctp_premarket_connect import should_auto_connect_now -from ctp_settings import is_ctp_auto_connect_enabled from vnpy_bridge import ctp_try_auto_reconnect logger = logging.getLogger(__name__) @@ -40,14 +39,7 @@ def start_ctp_reconnect_worker( def _loop() -> None: while True: try: - gs = get_setting_fn - if gs is None: - from fee_specs import get_setting as gs - if ( - is_ctp_auto_connect_enabled(gs) - and _auto_reconnect_enabled() - and should_auto_connect_now() - ): + if _auto_reconnect_enabled() and should_auto_connect_now(): mode = get_mode_fn() if ctp_try_auto_reconnect(mode): logger.debug("CTP 连接正常 [%s]", mode) diff --git a/ctp_settings.py b/ctp_settings.py index e660bc4..cdb236b 100644 --- a/ctp_settings.py +++ b/ctp_settings.py @@ -35,11 +35,11 @@ LIVE_FIELDS: tuple[tuple[str, str, str, str], ...] = ( PASSWORD_DB_KEYS = frozenset({"simnow_password", "ctp_live_password"}) CTP_AUTO_CONNECT_KEY = "ctp_auto_connect" -CTP_DISABLED_HINT = "CTP 自动连接已关闭(非交易时段建议关闭,避免反复连接失败)" +CTP_DISABLED_HINT = "CTP 自动连接已关闭(非交易时段不重连;开盘前 30 分钟仍会按计划连接)" def is_ctp_auto_connect_enabled(get_setting=None) -> bool: - """系统设置:是否允许 CTP 连接(含自动重连、盘前连接、手动连接)。""" + """系统设置:是否允许手动连接及非交易时段自动重连(盘前/交易时段计划连接不受此限制)。""" if get_setting is None: from fee_specs import get_setting as _gs diff --git a/install_trading.py b/install_trading.py index 5fee70c..ba68a8c 100644 --- a/install_trading.py +++ b/install_trading.py @@ -1667,10 +1667,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se threading.Thread(target=_warm, daemon=True, name="position-bootstrap").start() try: - if is_ctp_auto_connect_enabled(get_setting): - from vnpy_bridge import ctp_start_connect + from ctp_premarket_connect import should_auto_connect_now + from vnpy_bridge import ctp_start_connect + + if should_auto_connect_now(): mode = get_trading_mode(get_setting) - ctp_start_connect(mode, force=False) + ctp_start_connect(mode, force=False, scheduled=True) except Exception as exc: logger.debug("bootstrap ctp connect: %s", exc) diff --git a/static/js/trade.js b/static/js/trade.js index 2bff0ad..a7f856f 100644 --- a/static/js/trade.js +++ b/static/js/trade.js @@ -484,7 +484,7 @@ if (hint) { hint.textContent = ctpAutoConnectEnabled ? '断线自动重连 · 开盘前 30 分钟自动连接' - : 'CTP 自动连接已关闭(系统设置可开启)'; + : '自动连接已关闭 · 开盘前 30 分钟仍会按计划连接'; } if (btnConnect && !ctpAutoConnectEnabled) { btnConnect.disabled = true; diff --git a/templates/settings.html b/templates/settings.html index 777af31..bf35c46 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -271,7 +271,7 @@ CTP 自动连接 - 开启:盘前自动连接、断线重连、持仓页可连 CTP。关闭:立即断开所有 CTP 连接,不再尝试重连。 + 开启:盘前自动连接、断线重连、持仓页可连 CTP。关闭:立即断开,非交易时段不再重连;开盘前 30 分钟及交易时段仍会自动连接。 SimNow 非交易时段前置常不可用(与快期相同),建议收盘后关闭。 diff --git a/templates/trade.html b/templates/trade.html index 0cf014f..f746d2d 100644 --- a/templates/trade.html +++ b/templates/trade.html @@ -40,7 +40,7 @@ {% if not ctp_auto_connect %}disabled title="请先在系统设置 → CTP 连接 中开启自动连接"{% endif %}> {% if ctp_status.connected %}重连 CTP{% else %}连接 CTP{% endif %} - {% if ctp_auto_connect %}断线自动重连 · 开盘前 30 分钟自动连接{% else %}CTP 自动连接已关闭{% endif %} + {% if ctp_auto_connect %}断线自动重连 · 开盘前 30 分钟自动连接{% else %}自动连接已关闭 · 开盘前 30 分钟仍会按计划连接{% endif %} diff --git a/vnpy_bridge.py b/vnpy_bridge.py index 95667eb..5c8173e 100644 --- a/vnpy_bridge.py +++ b/vnpy_bridge.py @@ -575,10 +575,10 @@ class CtpBridge: "td_address": st.get("交易服务器", ""), } - def connect(self, mode: str, *, force: bool = False) -> None: - from ctp_settings import CTP_DISABLED_HINT, is_ctp_auto_connect_enabled + def connect(self, mode: str, *, force: bool = False, scheduled: bool = False) -> None: + from ctp_settings import CTP_DISABLED_HINT - if not is_ctp_auto_connect_enabled(): + if not _ctp_connect_permitted(scheduled=scheduled): self._last_error = CTP_DISABLED_HINT _persist_last_error(CTP_DISABLED_HINT) raise RuntimeError(CTP_DISABLED_HINT) @@ -680,11 +680,13 @@ class CtpBridge: finally: self._connect_in_progress = False - def start_connect_async(self, mode: str, *, force: bool = False) -> dict[str, Any]: + def start_connect_async( + self, mode: str, *, force: bool = False, scheduled: bool = False, + ) -> dict[str, Any]: """后台连接,不阻塞 HTTP 请求。""" - from ctp_settings import CTP_DISABLED_HINT, is_ctp_auto_connect_enabled + from ctp_settings import CTP_DISABLED_HINT - if not is_ctp_auto_connect_enabled(): + if not _ctp_connect_permitted(scheduled=scheduled): self._last_error = CTP_DISABLED_HINT _persist_last_error(CTP_DISABLED_HINT) return { @@ -709,7 +711,7 @@ class CtpBridge: def _run() -> None: try: - self.connect(mode, force=force) + self.connect(mode, force=force, scheduled=scheduled) except Exception as exc: logger.warning("CTP 后台连接失败: %s", exc) @@ -1915,6 +1917,19 @@ def vnpy_available() -> bool: return get_bridge().available() +def _ctp_connect_permitted(*, scheduled: bool = False) -> bool: + """scheduled=True:盘前/交易时段计划连接,不受「自动连接」开关限制。""" + from ctp_settings import is_ctp_auto_connect_enabled + + if is_ctp_auto_connect_enabled(): + return True + if not scheduled: + return False + from ctp_premarket_connect import should_auto_connect_now + + return should_auto_connect_now() + + def ctp_disconnect(*, set_disabled_hint: bool = False) -> None: """主动断开 CTP 并清理内存状态。""" from ctp_settings import CTP_DISABLED_HINT @@ -1935,19 +1950,17 @@ def ctp_connect(mode: str, *, force: bool = False) -> dict[str, Any]: return b.status(mode) -def ctp_start_connect(mode: str, *, force: bool = False) -> dict[str, Any]: +def ctp_start_connect(mode: str, *, force: bool = False, scheduled: bool = False) -> dict[str, Any]: """非阻塞发起连接,供 Web API 使用。""" b = get_bridge() - info = b.start_connect_async(mode, force=force) + info = b.start_connect_async(mode, force=force, scheduled=scheduled) st = b.status(mode) return {**info, "status": st} def ctp_try_auto_reconnect(mode: str) -> bool: """断线时静默异步重连;已连接且交易通道正常则不再重复 connect。""" - from ctp_settings import is_ctp_auto_connect_enabled - - if not is_ctp_auto_connect_enabled(): + if not _ctp_connect_permitted(scheduled=True): return False b = get_bridge() if not b.available(): @@ -1974,7 +1987,7 @@ def ctp_try_auto_reconnect(mode: str) -> bool: "请更新 SIMNOW_TD_ADDRESS 并确认服务器出网。" ) return False - info = b.start_connect_async(mode, force=False) + info = b.start_connect_async(mode, force=False, scheduled=True) return bool( info.get("connected") or info.get("connecting")