Merge pull request #290 from kevin9327/fix/cold-limit-price-zero-guard

fix(indicators): 维表涨跌停价为 0 时不再把全部标的判成涨停
This commit is contained in:
wshy
2026-09-10 09:10:43 +08:00
committed by GitHub
2 changed files with 67 additions and 0 deletions
+5
View File
@@ -808,9 +808,13 @@ def compute_limit_signals(
else:
authoritative_date = pl.col("date") == pl.col("date").max()
if "limit_up" in df.columns:
# >0 与实时路径 (_compute_limit_signals_today) 同守卫: 维表 limit_up 为 0
# (数据源未提供该字段的占位值) 不是权威价, 直接采用会让 raw_close >= -0.005
# 恒成立, 全部标的被判涨停。
effective_limit_up = pl.when(
authoritative_date
& pl.col("limit_up").is_not_null()
& (pl.col("limit_up") > 0)
& (pl.col("limit_up") < _SENTINEL)
).then(pl.col("limit_up")).otherwise(pl.col("_theoretical_limit_up"))
else:
@@ -819,6 +823,7 @@ def compute_limit_signals(
effective_limit_down = pl.when(
authoritative_date
& pl.col("limit_down").is_not_null()
& (pl.col("limit_down") > 0)
& (pl.col("limit_down") < _SENTINEL)
).then(pl.col("limit_down")).otherwise(pl.col("_theoretical_limit_down"))
else:
+62
View File
@@ -189,6 +189,68 @@ def test_daily_limit_prices_require_matching_instrument_date(instrument_as_of, e
assert "_instrument_as_of" not in result.columns
def test_daily_limit_prices_ignore_zero_placeholder_and_match_realtime():
"""维表涨跌停价为 0 (数据源未提供该字段的占位值) 时必须回退理论价。
直接采用 0 会让「raw_close >= 0 - 0.005」恒成立, 当日所有标的被判涨停,
连板数一路累加; 跌停侧反过来永远判不出跌停。实时路径
(_compute_limit_signals_today) 已有 >0 守卫, 冷路径必须同口径。
"""
instruments = pl.DataFrame({
"symbol": ["600001.SH"],
"name": ["普通股"],
"limit_up": [0.0],
"limit_down": [0.0],
"as_of": [date(2026, 7, 20)],
})
# 只涨 0.5%: 不是涨停
mild = pipeline.compute_limit_signals(
_daily_limit_rows(10.05),
instruments,
needed={"signal_limit_up", "consecutive_limit_ups"},
)
assert mild["signal_limit_up"][-1] is False
assert mild["consecutive_limit_ups"][-1] == 0
# 真涨停 11.00 = 10.00 x 1.1: 理论价兜底后仍须判出
sealed = pipeline.compute_limit_signals(
_daily_limit_rows(11.00),
instruments,
needed={"signal_limit_up", "consecutive_limit_ups"},
)
assert sealed["signal_limit_up"][-1] is True
assert sealed["consecutive_limit_ups"][-1] == 1
# 真跌停 9.00 = 10.00 x 0.9: 占位 0 不得让跌停漏判
floored = pipeline.compute_limit_signals(
_daily_limit_rows(9.00),
instruments,
needed={"signal_limit_down"},
)
assert floored["signal_limit_down"][-1] is True
# 与实时路径同一份维表同一结论
realtime = pipeline._compute_limit_signals_today(
pl.DataFrame({
"symbol": ["600001.SH"],
"date": [date(2026, 7, 20)],
"open": [10.05],
"high": [10.05],
"low": [10.05],
"close": [10.05],
"raw_close": [10.05],
"raw_high": [10.05],
"raw_low": [10.05],
"_prev_close_raw": [10.0],
"volume": [1000.0],
}),
instruments,
)
assert realtime["signal_limit_up"][0] is False
assert mild["signal_limit_up"][-1] is realtime["signal_limit_up"][0]
def test_realtime_limit_prices_ignore_stale_instrument_date():
today = date(2026, 7, 20)
rows = pl.DataFrame({