mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
Merge pull request #290 from kevin9327/fix/cold-limit-price-zero-guard
fix(indicators): 维表涨跌停价为 0 时不再把全部标的判成涨停
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user