Files
Justin Gu e374a0da28 release: v1.32.6 — 两周改动深度审查全面修复(回测口径三件套/LLM 安全加固/涨停价舍入/时区统一/缓存与竞态等 58 处)
对 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 全绿。
2026-09-06 22:16:48 +08:00

86 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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:3009:15 起集合竞价可看,11:30:30 容纳尾单撮合散点;
#: - 午盘 13:00:00-15:05:0015: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(),
}