mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
fix(backtest): 财务因子矩阵路径不再沿用上一期的过期值
矩阵侧逐列前向填充时跳过空值写入, 新一期财报缺某指标就继续沿用上一期, 同一行会混用两期报告 (pb 取新期净资产、roe 停在上一期); polars 侧 join_asof 只认最新一期整行, 该指标为 null。同一份配置两条路径给出不同 因子值。改为空值同样覆盖为 NaN, 与 attach_fundamental_factors 口径一致。
This commit is contained in:
@@ -181,9 +181,11 @@ def build_fundamental_matrices(
|
||||
continue
|
||||
for column, target in raw_columns.items():
|
||||
value = snapshot[column][row_index]
|
||||
if value is None or not np.isfinite(float(value)):
|
||||
continue
|
||||
target[start:, column_index] = float(value)
|
||||
numeric = float("nan") if value is None else float(value)
|
||||
# 新一期该指标为空时必须覆盖旧值为 NaN: 跳过写入会让同一行混用两期
|
||||
# 报告 (bps 取新期、roe 停在上一期), 与 polars 侧 join_asof 只认
|
||||
# 最新一期整行的口径不一致。
|
||||
target[start:, column_index] = numeric if np.isfinite(numeric) else np.nan
|
||||
|
||||
for name in requested:
|
||||
spec = FUNDAMENTAL_FACTORS[name]
|
||||
|
||||
@@ -140,6 +140,36 @@ def test_matrix_field_matches_polars_attach_across_two_announcements():
|
||||
np.testing.assert_allclose(actual, value, rtol=1e-6)
|
||||
|
||||
|
||||
def test_matrix_field_clears_value_when_newer_report_lacks_metric():
|
||||
"""新一期财报缺该指标时不得继续沿用上一期值, 否则同一行混用两期报告。
|
||||
|
||||
矩阵路径逐列前向填充, 若跳过空值写入, 4-16 起 pb 已换到新期 bps=6,
|
||||
roe 却仍停在上一期的 20 —— polars 侧 join_asof 只认最新一期整行 (roe 为
|
||||
null), 两条路径给出不同的因子值。
|
||||
"""
|
||||
panel = _daily_panel(date(2026, 4, 1), 20, ("600000.SH",))
|
||||
snapshot = _snapshot_frame([
|
||||
{"symbol": "600000.SH", "announce": "2026-04-05", "roe": 20.0, "bps": 4.0},
|
||||
{"symbol": "600000.SH", "announce": "2026-04-15", "roe": None, "bps": 6.0},
|
||||
])
|
||||
attached = attach_fundamental_factors(panel, snapshot, ["roe_latest", "pb_latest"]).sort("date")
|
||||
market = build_market_data_matrix(panel)
|
||||
matrices = build_fundamental_matrices(market, snapshot, ["roe_latest", "pb_latest"])
|
||||
column = market.symbols.index("600000.SH")
|
||||
|
||||
# polars 口径: 新公告生效 (4-16) 后 roe 无值, pb 走新一期 bps
|
||||
assert attached["roe_latest"].to_list()[15:] == [None] * 5
|
||||
assert all(value is not None for value in attached["pb_latest"].to_list()[15:])
|
||||
|
||||
for name in ("roe_latest", "pb_latest"):
|
||||
for row_index, value in enumerate(attached[name].to_list()):
|
||||
actual = matrices[name][row_index, column]
|
||||
if value is None:
|
||||
assert np.isnan(actual), (name, row_index, actual)
|
||||
else:
|
||||
np.testing.assert_allclose(actual, value, rtol=1e-6)
|
||||
|
||||
|
||||
def test_bps_nonpositive_gives_null_pb():
|
||||
panel = _daily_panel(date(2026, 4, 1), 8, ("000001.SZ",))
|
||||
snapshot = _snapshot_frame([
|
||||
|
||||
Reference in New Issue
Block a user