feat(data): vintage自检扩涨停池缺日防线——策略session共识「断档=不可逆损失须尽快」;per-date家族语义=每工作日(含节假日,空数据也写空文件)应有1个parquet,文件缺=当晚schtask漏跑=洞;洞≤42日历日(≈30交易日回补窗)→告警点名缺日+手动--start/--end回补指引,滑出窗=永久缺只入json不刷告警(防永久exit3疲劳);检查窗=首文件日→昨天(当日19:30才落盘不算洞);panel块进vintage_status.json契约(additive零破坏);+4测试(近窗洞告警/连续无洞/永久洞静默入json/目录缺不崩)13绿 [vps]
This commit is contained in:
@@ -2,8 +2,10 @@
|
||||
"""静态表 vintage 一致性自检测试 (2026-09-01 洗库撕裂事故防线④)。
|
||||
|
||||
核心契约: 跨表最大报告期不一致 → 告警 (退出码 3) + vintage_status.json
|
||||
落盘 (策略侧盘前预判数据源); 一致 → OK (退出码 0); 估值断更 → 告警。
|
||||
落盘 (策略侧盘前预判数据源); 一致 → OK (退出码 0); 估值断更 → 告警;
|
||||
涨停池三件套缺日 (09-02 入列): 可回补洞 → 告警, 永久缺 → 只入 json。
|
||||
"""
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
@@ -27,6 +29,34 @@ def _mk_table(root, name, stocks_periods):
|
||||
return d
|
||||
|
||||
|
||||
def _mk_ok_statements(root):
|
||||
for t in ("balance", "income", "cashflow"):
|
||||
_mk_table(root, t, {"a": ["2026-06-30"]})
|
||||
|
||||
|
||||
def _recent_weekdays(n, drop_dates=()):
|
||||
"""最近 n 个「昨天及以前」的工作日 (不写死日期), drop_dates 命中则跳过。"""
|
||||
out = []
|
||||
cur = datetime.date.today() - datetime.timedelta(days=1)
|
||||
while len(out) < n:
|
||||
if cur.weekday() < 5 and cur not in drop_dates:
|
||||
out.append(cur)
|
||||
cur -= datetime.timedelta(days=1)
|
||||
return out
|
||||
|
||||
|
||||
def _weekdays_since(days_back):
|
||||
"""今天-days_back → 昨天 区间内全部工作日 (含边界对齐)。"""
|
||||
out = []
|
||||
cur = datetime.date.today() - datetime.timedelta(days=days_back)
|
||||
end = datetime.date.today() - datetime.timedelta(days=1)
|
||||
while cur <= end:
|
||||
if cur.weekday() < 5:
|
||||
out.append(cur)
|
||||
cur += datetime.timedelta(days=1)
|
||||
return out
|
||||
|
||||
|
||||
# ======================== 单表扫描 ========================
|
||||
|
||||
class TestScanStatementTable:
|
||||
@@ -101,6 +131,58 @@ class TestCheckAll:
|
||||
assert any("估值表断更" in a for a in alerts)
|
||||
|
||||
|
||||
class TestPanelGapCheck:
|
||||
"""涨停池三件套缺日检查 (30 交易日回补窗, 滑出=永久缺不可逆)。"""
|
||||
|
||||
def _mk_dated_files(self, root, panel_type, dates):
|
||||
d = root / panel_type
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
for dt in dates:
|
||||
(d / f"{dt.strftime('%Y%m%d')}_{panel_type}.parquet").write_bytes(b"x" * 10)
|
||||
|
||||
def test_hole_within_window_alerts(self, tmp_path):
|
||||
"""近窗工作日缺 1 天 → 告警点名缺日 (schtask 漏跑)。"""
|
||||
_mk_ok_statements(tmp_path)
|
||||
hole = _recent_weekdays(5)[-1] # 5 个工作日前的那天挖掉
|
||||
dates = _recent_weekdays(30, drop_dates=(hole,))
|
||||
self._mk_dated_files(tmp_path, "zt_pool", dates)
|
||||
_, alerts = svc.check_all(tmp_path)
|
||||
assert any("zt_pool" in a and "缺日" in a
|
||||
and hole.isoformat() in a for a in alerts)
|
||||
|
||||
def test_all_present_no_alert(self, tmp_path):
|
||||
"""连续工作日全有 → 无缺日告警 (节假日空文件语义=文件存在)。"""
|
||||
_mk_ok_statements(tmp_path)
|
||||
self._mk_dated_files(tmp_path, "zt_pool", _recent_weekdays(30))
|
||||
_, alerts = svc.check_all(tmp_path)
|
||||
assert not any("缺日" in a for a in alerts)
|
||||
|
||||
def test_permanent_hole_no_alert_but_in_json(self, tmp_path):
|
||||
"""滑出回补窗的老洞 → 不刷告警 (避免永久 exit 3), 但入 json。
|
||||
|
||||
构造: 首文件=120 天前的工作日, 近 45 天工作日全有, 中段全缺 →
|
||||
中段洞均 >42 天 = 永久, 回补窗内无洞。
|
||||
"""
|
||||
_mk_ok_statements(tmp_path)
|
||||
old = datetime.date.today() - datetime.timedelta(days=120)
|
||||
while old.weekday() >= 5:
|
||||
old -= datetime.timedelta(days=1) # 对齐到工作日才是「应有文件」日
|
||||
dates = [old] + _weekdays_since(45)
|
||||
self._mk_dated_files(tmp_path, "zt_pool_dtgc", dates)
|
||||
status, alerts = svc.check_all(tmp_path)
|
||||
assert not any("zt_pool_dtgc" in a and "缺日" in a for a in alerts)
|
||||
assert old.isoformat() not in status["panel"]["zt_pool_dtgc"]["holes_permanent"]
|
||||
assert status["panel"]["zt_pool_dtgc"]["holes_permanent"] # 中段洞在列
|
||||
assert status["panel"]["zt_pool_dtgc"]["holes_backfillable"] == []
|
||||
|
||||
def test_missing_dir_no_crash_no_alert(self, tmp_path):
|
||||
"""目录不存在 (从未入列的环境) → 不崩不告警, json 记 n_files=0。"""
|
||||
_mk_ok_statements(tmp_path)
|
||||
status, alerts = svc.check_all(tmp_path)
|
||||
assert not any("缺日" in a for a in alerts)
|
||||
assert status["panel"]["zt_pool"]["n_files"] == 0
|
||||
|
||||
|
||||
# ======================== main / JSON 契约 ========================
|
||||
|
||||
class TestMain:
|
||||
|
||||
Reference in New Issue
Block a user