fix(screener): 涨停梯队按分区找前一交易日, 不再按固定自然日回看

春节后第一个交易日打开「涨停梯队」, 断板(晋级失败)一栏是空的, 炸板股的
板数也从「昨日 N 板 + 1」退回 1 板。

load_prior_consecutive 在 as_of 前 1~9 个自然日里找 enriched 日分区。春节
长假连着调休周末, 相邻两个交易日能隔 10~11 个自然日 (如 2024-02-08 到
2024-02-19), 固定窗口整段落空, 函数返回空表, 上游把 prev_consec 整列填 0:
is_failed = ~is_limit & ~is_broken & (prev_c > 0) 恒假, boards 里的
prev_c + 1 也恒为 1。

改成枚举 enriched 的 date= 分区、由近到远取早于 as_of 的日期, 与
auction_benchmark._prev_trading_day「本地日K分区日期 = 已知交易日集合」
同口径; 回看分区数上限保持 10, 缺列继续往前找的行为不变。
This commit is contained in:
kevin9327
2026-09-10 07:53:07 +09:00
parent d0a14b5c1b
commit 5da887b737
2 changed files with 121 additions and 5 deletions
+33 -5
View File
@@ -23,6 +23,9 @@ logger = logging.getLogger(__name__)
_history_cache: dict[tuple[str, date, int], tuple[float, pl.DataFrame]] = {}
_HISTORY_CACHE_TTL = 120.0 # 秒
# load_prior_consecutive 最多回看多少个已存在的日分区 (缺列时继续往前找的上限)
_PRIOR_PARTITION_SCAN = 10
@dataclass
class ScreenerResult:
@@ -109,15 +112,14 @@ class ScreenerService:
可直接从 parquet 读取, 无需 _load_enriched_for_date 的全量指标重算
(历史日期该慢路径最坏会触发 9 次全市场 compute_enriched_full)。
选取逻辑与旧循环等价: 在 as_of 前 1~9 天内找到第一个存在的日分区
(即前一交易日), 读取其 symbol + consec_col。存储列的值与重算值逐位一致
(连板计数为 run-length, 150 天 warmup 完全覆盖 A 股最长连板, 二者相等)。
由近到远取 as_of 之前已存在的日分区 (即前一交易日), 读取其
symbol + consec_col。存储列的值与重算值逐位一致 (连板计数为 run-length,
150 天 warmup 完全覆盖 A 股最长连板, 二者相等)。
返回列: symbol, prev_consec。找不到前一交易日时返回空 DataFrame。
"""
enriched_dir = self.repo.store.data_dir / self._enriched_dirname
for delta in range(1, 10):
candidate = as_of - timedelta(days=delta)
for candidate in self._prior_partition_dates(as_of, _PRIOR_PARTITION_SCAN):
target_parquet = enriched_dir / f"date={candidate.isoformat()}" / "part.parquet"
if not target_parquet.exists():
continue
@@ -140,6 +142,32 @@ class ScreenerService:
return pl.DataFrame()
return pl.DataFrame()
def _prior_partition_dates(self, as_of: date, limit: int) -> list[date]:
"""enriched 目录里早于 as_of 的分区日期, 由近到远最多 limit 个。
枚举分区目录而不是按自然日回看固定天数: 春节长假连着调休周末,
相邻两个交易日能隔 10~11 个自然日 (如 2024-02-08 → 2024-02-19),
固定窗口会整段落空。与 auction_benchmark._prev_trading_day
「本地日K分区日期 = 已知交易日集合」同口径。
"""
enriched_dir = self.repo.store.data_dir / self._enriched_dirname
days: list[date] = []
try:
entries = list(enriched_dir.iterdir())
except OSError:
return []
for part in entries:
if not part.name.startswith("date="):
continue
try:
day = date.fromisoformat(part.name[5:])
except ValueError:
continue
if day < as_of:
days.append(day)
days.sort(reverse=True)
return days[:limit]
def _compute_enriched_full(self, df_target: pl.DataFrame, target_date: date) -> pl.DataFrame:
"""从 14 列基础数据即时计算完整 enriched (含全部指标和信号)。