Schedule CTP disconnect 30 minutes after day and night session close.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+56
-15
@@ -3,7 +3,7 @@
|
|||||||
# 严禁用于:带单/代客理财、向他人推荐期货品种或买卖建议、融资配资等业务。
|
# 严禁用于:带单/代客理财、向他人推荐期货品种或买卖建议、融资配资等业务。
|
||||||
# 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md
|
# 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md
|
||||||
|
|
||||||
"""交易前自动连接 CTP(默认开盘前 30 分钟)。"""
|
"""CTP 按计划自动连接/断开:盘前 30 分钟连,日盘/夜盘收盘后 30 分钟断。"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -12,13 +12,19 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
from market_sessions import in_premarket_connect_window, is_trading_session
|
from market_sessions import (
|
||||||
from vnpy_bridge import ctp_start_connect, ctp_status
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
CHECK_INTERVAL_SEC = 60
|
CHECK_INTERVAL_SEC = 60
|
||||||
DEFAULT_MINUTES_BEFORE = 30
|
DEFAULT_MINUTES_BEFORE = 30
|
||||||
|
DEFAULT_MINUTES_AFTER = 30
|
||||||
|
|
||||||
|
|
||||||
def premarket_minutes_before() -> int:
|
def premarket_minutes_before() -> int:
|
||||||
@@ -28,7 +34,14 @@ def premarket_minutes_before() -> int:
|
|||||||
return DEFAULT_MINUTES_BEFORE
|
return DEFAULT_MINUTES_BEFORE
|
||||||
|
|
||||||
|
|
||||||
def _premarket_enabled() -> bool:
|
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 (
|
return (os.getenv("CTP_PREMARKET_CONNECT", "true") or "true").strip().lower() in (
|
||||||
"1",
|
"1",
|
||||||
"true",
|
"true",
|
||||||
@@ -36,14 +49,25 @@ def _premarket_enabled() -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
def should_auto_connect_now(*, minutes_before: int | None = None) -> bool:
|
||||||
"""交易时段内,或距下一段开盘 <= minutes_before 且尚未开盘。"""
|
"""是否应保持/发起 CTP 连接(供重连、权限判断复用)。"""
|
||||||
mins = premarket_minutes_before() if minutes_before is None else minutes_before
|
mins_b = premarket_minutes_before() if minutes_before is None else minutes_before
|
||||||
if is_trading_session():
|
mins_a = postmarket_minutes_after()
|
||||||
return True
|
if not _scheduled_connect_enabled() and not is_trading_session():
|
||||||
if not _premarket_enabled():
|
if not in_postmarket_grace_window(minutes_after=mins_a):
|
||||||
return False
|
return False
|
||||||
return in_premarket_connect_window(minutes_before=mins)
|
return should_keep_ctp_connected(
|
||||||
|
minutes_before=mins_b,
|
||||||
|
minutes_after=mins_a,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_ctp_premarket_connect_worker(
|
def start_ctp_premarket_connect_worker(
|
||||||
@@ -52,16 +76,20 @@ def start_ctp_premarket_connect_worker(
|
|||||||
get_setting_fn: Callable[[str, str], str] | None = None,
|
get_setting_fn: Callable[[str, str], str] | None = None,
|
||||||
interval: int = CHECK_INTERVAL_SEC,
|
interval: int = CHECK_INTERVAL_SEC,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""在交易开始前若干分钟自动发起 CTP 连接。"""
|
"""盘前自动连接;日盘/夜盘收盘宽限结束后自动断开。"""
|
||||||
|
|
||||||
def _loop() -> None:
|
def _loop() -> None:
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
while True:
|
while True:
|
||||||
sleep_sec = max(30, interval)
|
sleep_sec = max(30, interval)
|
||||||
try:
|
try:
|
||||||
if should_auto_connect_now():
|
mins_b = premarket_minutes_before()
|
||||||
|
mins_a = postmarket_minutes_after()
|
||||||
|
keep = should_auto_connect_now()
|
||||||
mode = get_mode_fn()
|
mode = get_mode_fn()
|
||||||
st = ctp_status(mode)
|
st = ctp_status(mode)
|
||||||
|
|
||||||
|
if keep:
|
||||||
if (
|
if (
|
||||||
not st.get("connected")
|
not st.get("connected")
|
||||||
and not st.get("connecting")
|
and not st.get("connecting")
|
||||||
@@ -71,18 +99,31 @@ def start_ctp_premarket_connect_worker(
|
|||||||
if info.get("started"):
|
if info.get("started"):
|
||||||
if is_trading_session():
|
if is_trading_session():
|
||||||
logger.info("交易时段内自动连接 CTP [%s]", mode)
|
logger.info("交易时段内自动连接 CTP [%s]", mode)
|
||||||
|
elif in_postmarket_grace_window(minutes_after=mins_a):
|
||||||
|
logger.info(
|
||||||
|
"盘后宽限期内保持/恢复 CTP 连接 [%s](收盘后 %d 分钟内)",
|
||||||
|
mode,
|
||||||
|
mins_a,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
"盘前自动连接 CTP [%s](开盘前 %d 分钟)",
|
"盘前自动连接 CTP [%s](开盘前 %d 分钟)",
|
||||||
mode,
|
mode,
|
||||||
premarket_minutes_before(),
|
mins_b,
|
||||||
)
|
)
|
||||||
if not is_trading_session() and in_premarket_connect_window(
|
if not is_trading_session() and in_premarket_connect_window(
|
||||||
minutes_before=premarket_minutes_before(),
|
minutes_before=mins_b,
|
||||||
):
|
):
|
||||||
sleep_sec = 30
|
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:
|
except Exception as exc:
|
||||||
logger.warning("CTP premarket connect worker: %s", exc)
|
logger.warning("CTP scheduled connect worker: %s", exc)
|
||||||
time.sleep(sleep_sec)
|
time.sleep(sleep_sec)
|
||||||
|
|
||||||
threading.Thread(target=_loop, daemon=True, name="ctp-premarket-connect").start()
|
threading.Thread(target=_loop, daemon=True, name="ctp-premarket-connect").start()
|
||||||
|
|||||||
@@ -246,3 +246,40 @@ def in_premarket_connect_window(
|
|||||||
if mins is None:
|
if mins is None:
|
||||||
return False
|
return False
|
||||||
return 0 < mins <= float(minutes_before)
|
return 0 < mins <= float(minutes_before)
|
||||||
|
|
||||||
|
|
||||||
|
def in_postmarket_grace_window(
|
||||||
|
now: Optional[datetime] = None,
|
||||||
|
*,
|
||||||
|
minutes_after: int = 30,
|
||||||
|
) -> bool:
|
||||||
|
"""日盘 15:00 或夜盘 02:30 收盘后 minutes_after 分钟内(仍保持连接,便于收尾)。"""
|
||||||
|
if is_trading_session(now):
|
||||||
|
return False
|
||||||
|
d = _normalize_dt(now)
|
||||||
|
t = _minutes_of_day(d)
|
||||||
|
wd = d.weekday()
|
||||||
|
ma = max(1, int(minutes_after))
|
||||||
|
day_close = 15 * 60
|
||||||
|
night_close = 2 * 60 + 30
|
||||||
|
# 日盘收盘 15:00 后宽限(周一至周五)
|
||||||
|
if wd < 5 and day_close <= t < day_close + ma:
|
||||||
|
return True
|
||||||
|
# 夜盘收盘 02:30 后宽限(含周六凌晨结束周五夜盘)
|
||||||
|
if night_close <= t < night_close + ma:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def should_keep_ctp_connected(
|
||||||
|
now: Optional[datetime] = None,
|
||||||
|
*,
|
||||||
|
minutes_before: int = 30,
|
||||||
|
minutes_after: int = 30,
|
||||||
|
) -> bool:
|
||||||
|
"""是否处于应连接 CTP 的窗口:交易时段 + 盘前 + 盘后宽限。"""
|
||||||
|
if is_trading_session(now):
|
||||||
|
return True
|
||||||
|
if in_postmarket_grace_window(now, minutes_after=minutes_after):
|
||||||
|
return True
|
||||||
|
return in_premarket_connect_window(now, minutes_before=minutes_before)
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
"""Verify CTP scheduled connect/disconnect windows."""
|
||||||
|
from datetime import datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
from market_sessions import (
|
||||||
|
in_premarket_connect_window,
|
||||||
|
in_postmarket_grace_window,
|
||||||
|
should_keep_ctp_connected,
|
||||||
|
)
|
||||||
|
|
||||||
|
TZ = ZoneInfo("Asia/Shanghai")
|
||||||
|
|
||||||
|
|
||||||
|
def chk(label, dt, exp_keep, exp_pre=None, exp_post=None):
|
||||||
|
keep = should_keep_ctp_connected(dt)
|
||||||
|
pre = in_premarket_connect_window(dt)
|
||||||
|
post = in_postmarket_grace_window(dt)
|
||||||
|
auto = keep
|
||||||
|
ok = keep == exp_keep
|
||||||
|
if exp_pre is not None:
|
||||||
|
ok = ok and pre == exp_pre
|
||||||
|
if exp_post is not None:
|
||||||
|
ok = ok and post == exp_post
|
||||||
|
print(
|
||||||
|
f"{'OK' if ok else 'FAIL'} {label} {dt.strftime('%a %H:%M')} "
|
||||||
|
f"keep={keep} pre={pre} post={post} auto={auto}"
|
||||||
|
)
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
cases = [
|
||||||
|
("日盘交易中", datetime(2026, 6, 30, 10, 0, tzinfo=TZ), True, False, False),
|
||||||
|
("上午小节休盘前10分", datetime(2026, 6, 30, 10, 20, tzinfo=TZ), True, True, False),
|
||||||
|
("日盘盘前28分", datetime(2026, 6, 30, 8, 32, tzinfo=TZ), True, True, False),
|
||||||
|
("日盘盘前31分", datetime(2026, 6, 30, 8, 29, tzinfo=TZ), False, False, False),
|
||||||
|
("日盘收盘后15分", datetime(2026, 6, 30, 15, 15, tzinfo=TZ), True, False, True),
|
||||||
|
("日盘收盘后35分", datetime(2026, 6, 30, 15, 35, tzinfo=TZ), False, False, False),
|
||||||
|
("夜盘盘前28分", datetime(2026, 6, 30, 20, 32, tzinfo=TZ), True, True, False),
|
||||||
|
("夜盘交易中", datetime(2026, 6, 30, 22, 0, tzinfo=TZ), True, False, False),
|
||||||
|
("夜盘收盘后15分", datetime(2026, 7, 1, 2, 45, tzinfo=TZ), True, False, True),
|
||||||
|
("夜盘收盘后35分", datetime(2026, 7, 1, 3, 5, tzinfo=TZ), False, False, False),
|
||||||
|
]
|
||||||
|
failed = sum(0 if chk(*c) else 1 for c in cases)
|
||||||
|
print("failed", failed)
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
{% if not ctp_auto_connect %}disabled title="请先在系统设置 → CTP 连接 中开启自动连接"{% endif %}>
|
{% if not ctp_auto_connect %}disabled title="请先在系统设置 → CTP 连接 中开启自动连接"{% endif %}>
|
||||||
{% if ctp_status.connected %}重连 CTP{% else %}连接 CTP{% endif %}
|
{% if ctp_status.connected %}重连 CTP{% else %}连接 CTP{% endif %}
|
||||||
</button>
|
</button>
|
||||||
<span class="text-muted trade-top-hint" id="ctp-auto-hint">{% if ctp_auto_connect %}断线自动重连 · 开盘前 30 分钟自动连接{% else %}自动连接已关闭 · 开盘前 30 分钟仍会按计划连接{% endif %}</span>
|
<span class="text-muted trade-top-hint" id="ctp-auto-hint">{% if ctp_auto_connect %}断线自动重连 · 开盘前 30 分钟连接 · 日盘/夜盘收盘后 30 分钟断开{% else %}手动连接已关闭 · 仍按交易时段计划自动连/断(盘前 30 分连、收盘 30 分后断){% endif %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user