# Copyright (c) 2025-2026 马建军. All rights reserved. # 专有软件 — 未经授权禁止复制、传播、转售。 # 严禁用于:带单/代客理财、向他人推荐期货品种或买卖建议、融资配资等业务。 # 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md """CTP 按计划自动连接/断开:盘前 30 分钟连,日盘/夜盘收盘后 30 分钟断。""" from __future__ import annotations import logging import os import threading import time from typing import Callable from market_sessions import ( in_premarket_connect_window, in_postmarket_grace_window, is_trading_session, should_keep_ctp_connected, ) from vnpy_bridge import ctp_disconnect, ctp_start_connect, ctp_status logger = logging.getLogger(__name__) CHECK_INTERVAL_SEC = 60 DEFAULT_MINUTES_BEFORE = 30 DEFAULT_MINUTES_AFTER = 30 def premarket_minutes_before() -> int: try: return max(5, int(os.getenv("CTP_PREMARKET_MINUTES", str(DEFAULT_MINUTES_BEFORE)))) except (TypeError, ValueError): return DEFAULT_MINUTES_BEFORE def postmarket_minutes_after() -> int: try: return max(5, int(os.getenv("CTP_POSTMARKET_MINUTES", str(DEFAULT_MINUTES_AFTER)))) except (TypeError, ValueError): return DEFAULT_MINUTES_AFTER def _scheduled_connect_enabled() -> bool: return (os.getenv("CTP_PREMARKET_CONNECT", "true") or "true").strip().lower() in ( "1", "true", "yes", ) def _scheduled_disconnect_enabled() -> bool: return (os.getenv("CTP_POSTMARKET_DISCONNECT", "true") or "true").strip().lower() in ( "1", "true", "yes", ) def should_auto_connect_now(*, minutes_before: int | None = None) -> bool: """是否应保持/发起 CTP 连接(供重连、权限判断复用)。""" mins_b = premarket_minutes_before() if minutes_before is None else minutes_before mins_a = postmarket_minutes_after() if not _scheduled_connect_enabled() and not is_trading_session(): if not in_postmarket_grace_window(minutes_after=mins_a): return False return should_keep_ctp_connected( minutes_before=mins_b, minutes_after=mins_a, ) def start_ctp_premarket_connect_worker( *, get_mode_fn: Callable[[], str], get_setting_fn: Callable[[str, str], str] | None = None, interval: int = CHECK_INTERVAL_SEC, ) -> None: """盘前自动连接;日盘/夜盘收盘宽限结束后自动断开。""" def _loop() -> None: time.sleep(10) while True: sleep_sec = max(30, interval) try: mins_b = premarket_minutes_before() mins_a = postmarket_minutes_after() keep = should_auto_connect_now() mode = get_mode_fn() st = ctp_status(mode) if keep: if ( not st.get("connected") and not st.get("connecting") and int(st.get("login_cooldown_sec") or 0) <= 0 ): info = ctp_start_connect(mode, force=False, scheduled=True) if info.get("started"): if is_trading_session(): logger.info("交易时段内自动连接 CTP [%s]", mode) elif in_postmarket_grace_window(minutes_after=mins_a): logger.info( "盘后宽限期内保持/恢复 CTP 连接 [%s](收盘后 %d 分钟内)", mode, mins_a, ) else: logger.info( "盘前自动连接 CTP [%s](开盘前 %d 分钟)", mode, mins_b, ) if not is_trading_session() and in_premarket_connect_window( minutes_before=mins_b, ): sleep_sec = 30 elif _scheduled_disconnect_enabled() and st.get("connected"): ctp_disconnect() logger.info( "盘后自动断开 CTP [%s](日盘/夜盘结束 %d 分钟后)", mode, mins_a, ) except Exception as exc: logger.warning("CTP scheduled connect worker: %s", exc) time.sleep(sleep_sec) threading.Thread(target=_loop, daemon=True, name="ctp-premarket-connect").start()