Files
qihuo/ctp_premarket_connect.py
T

130 lines
4.7 KiB
Python

# 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()