fix(factor): codex 尾批——_read_value_parquet 索引严格升序+唯一校验(embargo shift(-2) 以位置序为地基,乱序/重复件 structured reject 入缺源 skip) [nas]

This commit is contained in:
2026-10-10 15:05:52 +08:00
parent 3082db5a31
commit a0389e4f33
2 changed files with 29 additions and 0 deletions
+6
View File
@@ -186,12 +186,18 @@ def _read_value_parquet(path: str) -> pd.DataFrame | None:
"""读因子宽表 parquet;坏件/空件/索引不可转 datetime → None(codex 复审 M-5).
结构化剔除走缺源 skip 语义,不裸抛 AttributeError/ArrowInvalid.
索引须严格升序且唯一(codex 尾批):embargo 的 shift(-2) 以位置序为地基,
乱序/重复日期件拒收.
"""
try:
df = pd.read_parquet(path)
if df.empty or df.columns.empty:
return None
idx = pd.to_datetime(df.index)
if not (idx.is_monotonic_increasing and idx.is_unique):
print(f"[challenger] ⚠️ parquet 索引乱序/重复: {path} "
f"(monotonic={idx.is_monotonic_increasing}, unique={idx.is_unique})")
return None
except Exception as exc:
print(f"[challenger] ⚠️ parquet 不可读: {path} ({exc!r})")
return None
+23
View File
@@ -336,3 +336,26 @@ def test_redundancy_flags_per_day_unit():
assert ("x", "z") not in pairs and ("y", "z") not in pairs # 独立源不标
xy = next(f for f in flagged if (f["a"], f["b"]) == ("x", "y"))
assert xy["corr"] == pytest.approx(1.0, abs=1e-6)
def test_unsorted_or_dupe_index_parquet_rejected(tmp_path, monkeypatch, capsys):
"""codex 复审尾批:乱序/重复日期件 structured reject 并入缺源 skip.
embargo 的 shift(-2) 数学以位置序为地基,乱序/重复日期件必须拒."""
from sanguo_factor import challenger_lgbm as cl
idx = pd.date_range("2025-01-01", "2025-03-31", freq="B")
vdir = tmp_path / "vals"; vdir.mkdir()
good = pd.DataFrame(np.zeros((len(idx), 2)), index=idx, columns=["a", "b"])
good.to_parquet(vdir / "s0.parquet")
good.iloc[::-1].to_parquet(vdir / "s1.parquet") # 乱序:日期倒排
pd.concat([good.iloc[:1], good]).to_parquet(vdir / "s2.parquet") # 重复首日
monkeypatch.setattr(cl, "load_pool", lambda: {
n: {"direction": "+", "weight": 1.0 / 3} for n in ("s0", "s1", "s2")})
rc = cl.main(["--values-dir", str(vdir), "--as-of", "2025-03-31",
"--out-dir", str(tmp_path), "--vnpy-db", "fake.db"])
assert rc == 0
assert not (tmp_path / "challenger_lgbm").exists()
out = capsys.readouterr().out
assert "expected=3" in out and "loaded=1" in out
assert "s1" in out and "s2" in out
assert "乱序" in out