mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 13:24:18 +08:00
对 v1.21→v1.32.5 的 249 文件 4.2 万行改动做六路专项审查,本轮落地全部发现: 回测正确性:组合收益 fillna(0) 虚增、轮动停牌日过期价成交、单标的 WF 逐窗指标 被预热区稀释(三件套均带先红后绿回归);worst_drawdown 方向、grading 容错、 组合体检品种费率、寻优端点费率透传。 安全:LLM api_url 仅 http/https 且禁 userinfo(封死 file:// 读取与 Key 外送链)、 错误响应不回显原始 body、响应体 2MB 上限、配置原子写、坏配置字段级防御。 数据:涨跌停价整数分币舍入(67/318/90 个价位错 1 分漏判清零)、交易时段/采样/ provisional 统一沪时区、warehouse 增量缺口自动全量重拉、provisional 定点转正、 baostock 真故障抛错 + W/M 去 tradestatus(实测服务端报错,周月兜底此前从未工作) + 指数 vol 股→手(实测锚定)、ccpm 结构变更抛错。 Web API:缓存键补 count/vipdoc、NaN 清洗先于缓存、count>800 分页取全量、 submit 透传真实状态、pending 不再被淘汰成幽灵、watchlist/server 入参约束。 公式:FILTER 去副作用、0-1 值域误判收严、递归深度上限、REF 负移位显式禁止。 前端:4 处请求竞态序号守卫、Sparkline viewBox、北交所 market=2 映射、 空数据缓存死角、AI 弹窗卸载中止轮询、量能/资金日历口径修正。 CLI/CI:warehouse sync 失败 exit 1、参数校验干净报错、release 真实发布 SHA256、 CI 超时与缓存、spec 补 baostock 前提。 约 60 条回归测试先红后绿;pytest 1820 全过,ruff/mypy/vue-tsc/node --test 全绿。
86 lines
3.4 KiB
Python
86 lines
3.4 KiB
Python
"""A 股交易时段判断(共享工具)。
|
||
|
||
已有的两处会话过滤各自私有、口径不一:
|
||
|
||
- :mod:`easy_tdx.realtime.feed` 的 ``_DEFAULT_SESSIONS``(09:15-11:30 / 13:00-15:00,
|
||
WS 按需轮询用,收盘竞价不拉);
|
||
- :mod:`easy_tdx.web.quote_streamer` 的 ``_is_trading_hours``(09:10-15:10 连续窗,
|
||
SSE 快照轮询用,午休也降频拉收盘价快照)。
|
||
|
||
本模块提供第三个口径——**WebUI 仪表盘自动刷新用的"有效行情时段"**:
|
||
在 feed 的窗口基础上,早盘前移到 09:15(集合竞价有行情),尾盘后移到
|
||
15:05(收盘集合竞价 15:00-15:03 仍有成交),午休排除。前端在此时段内
|
||
做 15-30s 轮询,之外暂停自动刷新(手动刷新不受限)。
|
||
|
||
不改动上述两处既有语义,避免影响它们的测试与行为。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime, time, timedelta, timezone, tzinfo
|
||
from typing import Any
|
||
|
||
__all__ = [
|
||
"SHANGHAI_TZ",
|
||
"SESSION_WINDOWS",
|
||
"SESSION_DESC",
|
||
"is_trading_time",
|
||
"session_info",
|
||
]
|
||
|
||
#: A 股行情统一按沪市时区判断。中国无夏令时,固定 UTC+8 即可精确表达,
|
||
#: 不依赖系统时区/zoneinfo 数据库(Windows 无 IANA tzdata)。
|
||
SHANGHAI_TZ = timezone(timedelta(hours=8), "Asia/Shanghai")
|
||
|
||
#: 有效行情时段(沪市时间)。窗口 = (start, end),含两端。
|
||
#: - 早盘 09:15:00-11:30:30:09:15 起集合竞价可看,11:30:30 容纳尾单撮合散点;
|
||
#: - 午盘 13:00:00-15:05:00:15:00-15:03 为收盘集合竞价,留 2 分钟余量。
|
||
SESSION_WINDOWS: tuple[tuple[time, time], ...] = (
|
||
(time(9, 15, 0), time(11, 30, 30)),
|
||
(time(13, 0, 0), time(15, 5, 0)),
|
||
)
|
||
|
||
#: 展示用时段描述(前端状态栏 / API 响应)。
|
||
SESSION_DESC = "09:15~11:30, 13:00~15:05"
|
||
|
||
|
||
def is_trading_time(now: datetime | None = None, *, tz: tzinfo | None = None) -> bool:
|
||
"""判断当前是否处于 A 股有效行情时段(周一至周五,午休与深夜除外)。
|
||
|
||
只做"星期 + 时分"判断,不含法定节假日日历——节假日全天处于闭市
|
||
窗口外时前端轮询暂停是安全方向(误刷新无副作用,漏刷新才是问题,
|
||
而节假日行情本就不动,手动刷新始终可用)。
|
||
|
||
Args:
|
||
now: 待判断时间,None = 取当前时间。
|
||
tz: 未传 ``now`` 时使用的时区,None = :data:`SHANGHAI_TZ`(与主机
|
||
时区无关;非中国时区的服务器/海外机器不会错位)。
|
||
|
||
Returns:
|
||
True = 盘中(含集合竞价缓冲窗)。
|
||
"""
|
||
t = now or datetime.now(tz=tz or SHANGHAI_TZ)
|
||
if t.weekday() >= 5: # 周六/周日
|
||
return False
|
||
for start, end in SESSION_WINDOWS:
|
||
if start <= t.time() <= end:
|
||
return True
|
||
return False
|
||
|
||
|
||
def session_info(now: datetime | None = None, *, tz: tzinfo | None = None) -> dict[str, Any]:
|
||
"""构建 /market/session 响应体:时段判断 + 窗口描述 + 服务器时间。
|
||
|
||
前端以本地判断为主(每 15s 重估),本接口用于校准服务器侧视角。
|
||
"""
|
||
t = now or datetime.now(tz=tz or SHANGHAI_TZ)
|
||
return {
|
||
"is_trading_time": is_trading_time(t),
|
||
"sessions": [
|
||
{"start": s.strftime("%H:%M"), "end": e.strftime("%H:%M")} for s, e in SESSION_WINDOWS
|
||
],
|
||
"session_desc": SESSION_DESC,
|
||
"server_time": t.isoformat(timespec="seconds"),
|
||
"weekday": t.weekday(),
|
||
}
|