fix: 题材热点缓存改为开盘前失效,避免开盘后显示上个交易日数据

非交易时段(凌晨/早盘前/周末)访问题材接口时,旧逻辑把上个交易日
数据缓存 18 小时,导致开盘后缓存未过期仍读到旧数据。

- 非交易时段缓存 TTL 由固定 18h 改为"截止到下一次开盘时刻":
  早盘前→9:30 失效、午休→13:00 失效、收盘后/周末→下一交易日 9:30 失效
- 题材列表交易时段强制不读缓存,双保险实时拉取
- 热点穿透图/题材股票缓存同样收敛到开盘前失效

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-10 13:26:00 +08:00
co-authored by Claude
parent a1736c8a17
commit 1550291d6b
+35 -11
View File
@@ -55,9 +55,31 @@ def _is_trading_time() -> bool:
or _TRADING_AFTERNOON[0] <= t <= _TRADING_AFTERNOON[1])
def _dynamic_ttl() -> int:
"""盘中返回 2 分钟缓存 TTL,非交易时段 18 小时(覆盖到下一交易日)"""
return 0 if _is_trading_time() else 18
def _next_open_delta_seconds() -> int:
"""非交易时段写入的缓存距下次开盘的秒数。
缓存只允许存活到下一次开盘(早盘 9:30 / 午休后 13:00)前一刻,
保证交易日开盘后缓存必然过期并实时拉取,不会读到上个交易日写入的旧数据。
"""
now = datetime.now(_CST)
# 午休 11:30-13:00 → 截止今天 13:00
if _TRADING_MORNING[1] < now.time() < _TRADING_AFTERNOON[0]:
open_dt = now.replace(hour=13, minute=0, second=0, microsecond=0)
return max(0, int((open_dt - now).total_seconds()))
# 其余非交易时段(早盘前 / 收盘后 / 周末 / 节假日)→ 下一个工作日 9:30
for days in range(0, 8):
d = (now + timedelta(days=days)).date()
if d.weekday() >= 5: # 跳过周末
continue
open_dt = datetime(d.year, d.month, d.day, 9, 30, tzinfo=_CST)
if open_dt > now:
return max(0, int((open_dt - now).total_seconds()))
return 0
def _list_ttl_seconds() -> int:
"""题材列表缓存秒数:交易时段 0(不缓存、实时拉取);非交易时段缓存到下次开盘前失效"""
return 0 if _is_trading_time() else _next_open_delta_seconds()
# ---- 请求封装 ----
@@ -126,9 +148,11 @@ async def fetch_theme_list(sort_field: int = 1, asc: bool = False) -> list[dict]
asc: True=升序, False=降序
"""
cache_key = f"theme_list:{sort_field}:{asc}"
cached = get_cache(cache_key)
if cached is not None:
return json.loads(cached)
# 交易时段强制实时:跳过缓存读取,避免命中非交易时段写入的上个交易日旧数据
if not _is_trading_time():
cached = get_cache(cache_key)
if cached is not None:
return json.loads(cached)
sort = 1 if asc else -1
# hotRank 数值越小越热,"热度降序(最热在前)" 需反转为接口升序
@@ -156,9 +180,9 @@ async def fetch_theme_list(sort_field: int = 1, asc: bool = False) -> list[dict]
page += 1
if items:
ttl = _dynamic_ttl()
if ttl > 0:
set_cache(cache_key, json.dumps(items, ensure_ascii=False), ttl_hours=ttl)
ttl_s = _list_ttl_seconds()
if ttl_s > 0:
set_cache(cache_key, json.dumps(items, ensure_ascii=False), ttl_seconds=ttl_s)
return items
@@ -234,8 +258,8 @@ _GRAPH_CACHE_SECONDS = 60
def _graph_ttl_seconds() -> int:
"""图聚合结果与题材股票子层的缓存秒数:盘中 60 秒非盘中 18 小时"""
return _GRAPH_CACHE_SECONDS if _is_trading_time() else 18 * 3600
"""图聚合结果与题材股票子层的缓存秒数:盘中 60 秒非盘中缓存到下次开盘前失效"""
return _GRAPH_CACHE_SECONDS if _is_trading_time() else _next_open_delta_seconds()
# 后台重建锁:cache_key -> asyncio.Lock,幂等去重,防止并发重复聚合