Add personal license agreement and rename product section to tradable symbols.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+107
-102
@@ -1,102 +1,107 @@
|
||||
"""国内期货交易时段与盘前连接窗口。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
TZ = ZoneInfo("Asia/Shanghai")
|
||||
|
||||
# 各交易段开盘时刻 (时, 分)
|
||||
SESSION_OPENS = (
|
||||
(9, 0),
|
||||
(13, 30),
|
||||
(21, 0),
|
||||
)
|
||||
|
||||
|
||||
def is_trading_session(now: Optional[datetime] = None) -> bool:
|
||||
d = now or datetime.now(TZ)
|
||||
if d.tzinfo is None:
|
||||
d = d.replace(tzinfo=TZ)
|
||||
else:
|
||||
d = d.astimezone(TZ)
|
||||
wd = d.weekday()
|
||||
if wd == 6:
|
||||
return False
|
||||
if wd == 5 and d.hour < 21:
|
||||
return False
|
||||
t = d.hour * 60 + d.minute
|
||||
def in_range(sh: int, sm: int, eh: int, em: int) -> bool:
|
||||
return t >= sh * 60 + sm and t < eh * 60 + em
|
||||
if in_range(9, 0, 11, 30):
|
||||
return True
|
||||
if in_range(13, 30, 15, 0):
|
||||
return True
|
||||
if in_range(21, 0, 24, 0):
|
||||
return True
|
||||
if in_range(0, 0, 2, 30):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _session_open_allowed(day: datetime, hour: int, minute: int) -> bool:
|
||||
wd = day.weekday()
|
||||
if (hour, minute) == (9, 0) or (hour, minute) == (13, 30):
|
||||
return wd < 5
|
||||
if (hour, minute) == (21, 0):
|
||||
if wd < 5:
|
||||
return True
|
||||
return wd == 5
|
||||
return False
|
||||
|
||||
|
||||
def iter_session_starts(
|
||||
start: datetime,
|
||||
*,
|
||||
hours_ahead: int = 36,
|
||||
) -> list[datetime]:
|
||||
"""列出 start 之后若干小时内的各段开盘时刻。"""
|
||||
if start.tzinfo is None:
|
||||
start = start.replace(tzinfo=TZ)
|
||||
else:
|
||||
start = start.astimezone(TZ)
|
||||
end = start + timedelta(hours=hours_ahead)
|
||||
out: list[datetime] = []
|
||||
day = start.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
while day <= end:
|
||||
for h, m in SESSION_OPENS:
|
||||
if not _session_open_allowed(day, h, m):
|
||||
continue
|
||||
dt = day.replace(hour=h, minute=m)
|
||||
if dt > start and dt <= end:
|
||||
out.append(dt)
|
||||
day += timedelta(days=1)
|
||||
out.sort()
|
||||
return out
|
||||
|
||||
|
||||
def minutes_until_next_session(now: Optional[datetime] = None) -> Optional[float]:
|
||||
d = now or datetime.now(TZ)
|
||||
if d.tzinfo is None:
|
||||
d = d.replace(tzinfo=TZ)
|
||||
else:
|
||||
d = d.astimezone(TZ)
|
||||
starts = iter_session_starts(d, hours_ahead=48)
|
||||
if not starts:
|
||||
return None
|
||||
return (starts[0] - d).total_seconds() / 60.0
|
||||
|
||||
|
||||
def in_premarket_connect_window(
|
||||
now: Optional[datetime] = None,
|
||||
*,
|
||||
minutes_before: int = 30,
|
||||
) -> bool:
|
||||
"""距下一段开盘 <= minutes_before 分钟,且当前尚未进入交易时段。"""
|
||||
if is_trading_session(now):
|
||||
return False
|
||||
mins = minutes_until_next_session(now)
|
||||
if mins is None:
|
||||
return False
|
||||
return 0 < mins <= float(minutes_before)
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
# 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
# 严禁用于:带单/代客理财、向他人推荐期货品种或买卖建议、融资配资等业务。
|
||||
# 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md
|
||||
|
||||
"""国内期货交易时段与盘前连接窗口。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
TZ = ZoneInfo("Asia/Shanghai")
|
||||
|
||||
# 各交易段开盘时刻 (时, 分)
|
||||
SESSION_OPENS = (
|
||||
(9, 0),
|
||||
(13, 30),
|
||||
(21, 0),
|
||||
)
|
||||
|
||||
|
||||
def is_trading_session(now: Optional[datetime] = None) -> bool:
|
||||
d = now or datetime.now(TZ)
|
||||
if d.tzinfo is None:
|
||||
d = d.replace(tzinfo=TZ)
|
||||
else:
|
||||
d = d.astimezone(TZ)
|
||||
wd = d.weekday()
|
||||
if wd == 6:
|
||||
return False
|
||||
if wd == 5 and d.hour < 21:
|
||||
return False
|
||||
t = d.hour * 60 + d.minute
|
||||
def in_range(sh: int, sm: int, eh: int, em: int) -> bool:
|
||||
return t >= sh * 60 + sm and t < eh * 60 + em
|
||||
if in_range(9, 0, 11, 30):
|
||||
return True
|
||||
if in_range(13, 30, 15, 0):
|
||||
return True
|
||||
if in_range(21, 0, 24, 0):
|
||||
return True
|
||||
if in_range(0, 0, 2, 30):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _session_open_allowed(day: datetime, hour: int, minute: int) -> bool:
|
||||
wd = day.weekday()
|
||||
if (hour, minute) == (9, 0) or (hour, minute) == (13, 30):
|
||||
return wd < 5
|
||||
if (hour, minute) == (21, 0):
|
||||
if wd < 5:
|
||||
return True
|
||||
return wd == 5
|
||||
return False
|
||||
|
||||
|
||||
def iter_session_starts(
|
||||
start: datetime,
|
||||
*,
|
||||
hours_ahead: int = 36,
|
||||
) -> list[datetime]:
|
||||
"""列出 start 之后若干小时内的各段开盘时刻。"""
|
||||
if start.tzinfo is None:
|
||||
start = start.replace(tzinfo=TZ)
|
||||
else:
|
||||
start = start.astimezone(TZ)
|
||||
end = start + timedelta(hours=hours_ahead)
|
||||
out: list[datetime] = []
|
||||
day = start.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
while day <= end:
|
||||
for h, m in SESSION_OPENS:
|
||||
if not _session_open_allowed(day, h, m):
|
||||
continue
|
||||
dt = day.replace(hour=h, minute=m)
|
||||
if dt > start and dt <= end:
|
||||
out.append(dt)
|
||||
day += timedelta(days=1)
|
||||
out.sort()
|
||||
return out
|
||||
|
||||
|
||||
def minutes_until_next_session(now: Optional[datetime] = None) -> Optional[float]:
|
||||
d = now or datetime.now(TZ)
|
||||
if d.tzinfo is None:
|
||||
d = d.replace(tzinfo=TZ)
|
||||
else:
|
||||
d = d.astimezone(TZ)
|
||||
starts = iter_session_starts(d, hours_ahead=48)
|
||||
if not starts:
|
||||
return None
|
||||
return (starts[0] - d).total_seconds() / 60.0
|
||||
|
||||
|
||||
def in_premarket_connect_window(
|
||||
now: Optional[datetime] = None,
|
||||
*,
|
||||
minutes_before: int = 30,
|
||||
) -> bool:
|
||||
"""距下一段开盘 <= minutes_before 分钟,且当前尚未进入交易时段。"""
|
||||
if is_trading_session(now):
|
||||
return False
|
||||
mins = minutes_until_next_session(now)
|
||||
if mins is None:
|
||||
return False
|
||||
return 0 < mins <= float(minutes_before)
|
||||
|
||||
Reference in New Issue
Block a user