Files
tick-stock-panel/backend/tests/test_overview_dimension_leader.py
kevin9327 7755ab3a7d fix(overview): 板块领涨股不再把 0.00% 当成缺失涨跌幅
_dimension_rank 用 `_finite(...) or -999` 作为排序键, 0.00% 是假值,
会被替换成 -999 排到所有下跌股之后。板块整体下跌、最强的一只恰好平盘时,
看板/复盘的「领涨」显示成跌幅最小的下跌股, 而不是那只平盘股。

改为显式区分 None 与 0.0 (缺涨跌幅 → -inf), 与 sector_monitor
_dimension_snapshot 的 `max(valid_rows, key=lambda row: row["change_pct"])`
同口径。
2026-09-10 19:32:13 +09:00

90 lines
3.2 KiB
Python

"""市场总览「领涨股」选取: 涨跌幅 0.00% 是有效值, 不能被当成缺失值。"""
from __future__ import annotations
import json
import polars as pl
from app.services.market_overview_builder import _dimension_rank
def _fake_repo(tmp_path):
import types
return types.SimpleNamespace(store=types.SimpleNamespace(data_dir=tmp_path))
def _write_concept_ext(tmp_path, mapping: dict[str, str]) -> None:
"""写一张 snapshot 模式的概念扩展表 (symbol → 所属概念)。"""
cfg_dir = tmp_path / "ext_data" / "concept_tbl"
cfg_dir.mkdir(parents=True, exist_ok=True)
cfg_dir.joinpath("config.json").write_text(
json.dumps({
"id": "concept_tbl",
"label": "概念表",
"mode": "snapshot",
"fields": [{"name": "所属概念", "dtype": "string", "label": "所属概念"}],
}, ensure_ascii=False),
encoding="utf-8",
)
pl.DataFrame({
"symbol": list(mapping.keys()),
"所属概念": list(mapping.values()),
}).write_parquet(cfg_dir / "part.parquet")
def test_flat_stock_can_be_leader_of_a_falling_concept(tmp_path):
"""全概念下跌、最强的一只恰好平盘(0.00%)时, 领涨股必须是那只平盘股。"""
_write_concept_ext(tmp_path, {
"000001.SZ": "人工智能",
"000002.SZ": "人工智能",
"000003.SZ": "人工智能",
})
rows = [
{"symbol": "000001.SZ", "name": "跌一", "change_pct": -0.01, "amount": 1e8},
{"symbol": "000002.SZ", "name": "平盘", "change_pct": 0.0, "amount": 2e8},
{"symbol": "000003.SZ", "name": "跌三", "change_pct": -0.03, "amount": 3e8},
]
result = _dimension_rank(rows, _fake_repo(tmp_path), "concept")
items = {item["name"]: item for item in result["lagging"]}
assert "人工智能" in items
leader = items["人工智能"]["leader"]
assert leader["name"] == "平盘"
assert leader["change_pct"] == 0.0
def test_leader_falls_back_to_none_pct_last(tmp_path):
"""change_pct 缺失(None)的成分股仍排在所有有值的成分股之后。"""
_write_concept_ext(tmp_path, {
"000001.SZ": "芯片",
"000002.SZ": "芯片",
})
rows = [
{"symbol": "000001.SZ", "name": "无行情", "change_pct": None, "amount": 1e8},
{"symbol": "000002.SZ", "name": "微跌", "change_pct": -0.02, "amount": 1e8},
]
result = _dimension_rank(rows, _fake_repo(tmp_path), "concept")
items = {item["name"]: item for item in result["lagging"]}
assert items["芯片"]["leader"]["name"] == "微跌"
def test_leader_still_picks_max_when_all_positive(tmp_path):
"""普通情形不受影响: 全部上涨时仍取涨幅最大的一只。"""
_write_concept_ext(tmp_path, {
"000001.SZ": "光伏",
"000002.SZ": "光伏",
})
rows = [
{"symbol": "000001.SZ", "name": "涨多", "change_pct": 0.05, "amount": 1e8},
{"symbol": "000002.SZ", "name": "涨少", "change_pct": 0.01, "amount": 1e8},
]
result = _dimension_rank(rows, _fake_repo(tmp_path), "concept")
items = {item["name"]: item for item in result["leading"]}
assert items["光伏"]["leader"]["name"] == "涨多"