Files
tick-stock-panel/backend/tests/test_enriched_stale_price_partition.py
shy3130 e0cd625ef4 feat(platform): 因子平台与因子↔策略双向联动 v0.2.3
- 因子平台: /factors 一级页(检验/因子库/编辑器/组合/挖掘), DSL 公式因子(25 算子点选、双语字段、我的因子模板、脏公式守卫), 版本与生命周期, 自动挖掘 L1 统计筛选
- 因子↔策略四条桥: 触发器 Zap 快建因子条件信号、因子一键生成排名策略、自定义信号 AI 提示词接入因子分组、策略回测因子归因(胜/败单入场信号日因子均值, 独立 tab, 双语因子名)
- 回测: 统计卡新增盈亏比(≥1 红/<1 绿), 蒙卡回撤合并为中位/95% 双值卡(自适应字号), 高级设置基础过滤与策略编辑器参数对齐(5 组区间)
- 信号库独立页 /signals(原设置 tab 迁出), 持仓提醒入导航; 挖掘并入因子页第 5 tab, /mining 旧链接重定向
- 研究线配套: 因子目录 61→77(评分/矩阵双内核), stats_v2(Newey-West/BH-FDR/DSR), enriched 管道与异动/报价服务配套调整
- 文档: README 导航与特性表、features.md 因子平台章节、操作说明书 9.2、factor-platform-plan 执行状态与 §5、二开文档桥接说明; 交流与支持节改版
- 版本 0.2.2 → 0.2.3; 后端全量 1625 passed(1 例环境性跳过), 前端 build 通过
2026-09-05 15:41:15 +08:00

84 lines
3.8 KiB
Python

"""收盘价过期分区回归: 盘后管道必须重算"行数完整但价格停留在竞价前"的 enriched 分区。
实时 flush 可能在收盘集合竞价结果发布前写入当日分区 (实测 2026-09-04: TickFlow
实时端点收盘后仍返回旧价, 3392/5554 只股票 enriched 收盘价与官方日线不符),
分区行数与 daily 相同, #223 的行数校验识别不到, 增量路径不会重算, 当日偏离值/
动量等全部 enriched 消费方都会用旧价 (海鸥住工 10 日偏离 98.77% vs 官方 99.61%)。
_prune_stale_price_partitions 按官方日线做 raw_close vs close 值级比对, 不一致即
删分区, 让增量重算按官方日线全市场重建。
"""
from __future__ import annotations
from pathlib import Path
import polars as pl
from app.jobs.daily_pipeline import _prune_stale_price_partitions
def _write_partition(base: Path, day: str, symbols: list[str], closes: list[float], col: str) -> None:
part = base / f"date={day}"
part.mkdir(parents=True, exist_ok=True)
pl.DataFrame({"symbol": symbols, col: closes}).write_parquet(part / "part.parquet")
def test_stale_close_partition_is_pruned(tmp_path) -> None:
daily = tmp_path / "kline_daily"
enriched = tmp_path / "kline_daily_enriched"
syms = ["002084.SZ", "600519.SH", "000001.SZ"]
# 2026-09-04: 官方日线收盘 7.10, 实时写入的 enriched 停留在竞价前 7.07;
# 行数两侧一致 (覆盖全), #223 行数校验识别不到 (issue 实测形态)
_write_partition(daily, "2026-09-04", syms, [7.10, 1330.10, 11.89], "close")
_write_partition(enriched, "2026-09-04", syms, [7.07, 1330.10, 11.89], "raw_close")
pruned = _prune_stale_price_partitions(daily, enriched)
assert pruned == ["2026-09-04"]
assert not (enriched / "date=2026-09-04").exists()
def test_matching_close_partitions_untouched(tmp_path) -> None:
daily = tmp_path / "kline_daily"
enriched = tmp_path / "kline_daily_enriched"
syms = ["002084.SZ", "600519.SH"]
_write_partition(daily, "2026-09-04", syms, [7.10, 1330.10], "close")
_write_partition(enriched, "2026-09-04", syms, [7.10, 1330.10], "raw_close")
assert _prune_stale_price_partitions(daily, enriched) == []
assert (enriched / "date=2026-09-04" / "part.parquet").exists()
def test_multiple_stale_dates_all_pruned(tmp_path) -> None:
# 管道连续数日未触发重建时, 最近多个交易日的过期分区一并修复
daily = tmp_path / "kline_daily"
enriched = tmp_path / "kline_daily_enriched"
for day, stale in [("2026-09-04", 7.07), ("2026-09-03", 6.88), ("2026-09-02", 6.80)]:
_write_partition(daily, day, ["002084.SZ"], [stale + 0.03], "close")
_write_partition(enriched, day, ["002084.SZ"], [stale], "raw_close")
pruned = _prune_stale_price_partitions(daily, enriched)
assert sorted(pruned) == ["2026-09-02", "2026-09-03", "2026-09-04"]
def test_missing_raw_close_column_left_alone(tmp_path) -> None:
# 旧 schema 无 raw_close 列 → 读列失败, 交给既有完整性检查, 不误删
daily = tmp_path / "kline_daily"
enriched = tmp_path / "kline_daily_enriched"
_write_partition(daily, "2026-09-04", ["002084.SZ"], [7.10], "close")
part = enriched / "date=2026-09-04"
part.mkdir(parents=True)
pl.DataFrame({"symbol": ["002084.SZ"], "close": [7.07]}).write_parquet(part / "part.parquet")
assert _prune_stale_price_partitions(daily, enriched) == []
assert (part / "part.parquet").exists()
def test_daily_partition_missing_left_alone(tmp_path) -> None:
# 官方日线尚未同步的日期不比对 (留给当日正常流程)
enriched = tmp_path / "kline_daily_enriched"
_write_partition(enriched, "2026-09-04", ["002084.SZ"], [7.07], "raw_close")
assert _prune_stale_price_partitions(tmp_path / "kline_daily", enriched) == []
assert (enriched / "date=2026-09-04" / "part.parquet").exists()