From 1e15e8059dcd68b1da7a883fa39f86eb4ac74a47 Mon Sep 17 00:00:00 2001 From: yuxuanhui Date: Sun, 30 Aug 2026 01:50:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(sector-radar):=20=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E8=82=A1=E7=A5=A8=E5=B8=82=E5=9C=BA=E5=AD=97=E6=AE=B5=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/sector_radar/domain/normalize.py | 3 +- .../modules/sector_radar/domain/source.py | 4 +-- .../tests/unit/sector_radar/test_normalize.py | 31 +++++++++++++++++-- .../unit/sector_radar/test_tushare_source.py | 3 +- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/zhixing-server/src/zhixing_server/modules/sector_radar/domain/normalize.py b/zhixing-server/src/zhixing_server/modules/sector_radar/domain/normalize.py index f1dd6c9..0c4dda7 100644 --- a/zhixing-server/src/zhixing_server/modules/sector_radar/domain/normalize.py +++ b/zhixing-server/src/zhixing_server/modules/sector_radar/domain/normalize.py @@ -202,7 +202,8 @@ def _is_lifecycle_candidate(stock: StockBasicRow, target: date) -> bool: return False if stock.symbol.startswith(("200", "900")): return False - if "北交" in stock.market or "B股" in stock.market.upper(): + market = stock.market or "" + if "北交" in market or "B股" in market.upper(): return False if stock.list_date is None or stock.list_date > target: return False diff --git a/zhixing-server/src/zhixing_server/modules/sector_radar/domain/source.py b/zhixing-server/src/zhixing_server/modules/sector_radar/domain/source.py index 347d958..991c255 100644 --- a/zhixing-server/src/zhixing_server/modules/sector_radar/domain/source.py +++ b/zhixing-server/src/zhixing_server/modules/sector_radar/domain/source.py @@ -342,7 +342,7 @@ class StockBasicRow: ts_code: str symbol: str name: str - market: str + market: str | None exchange: str list_status: str list_date: date | None @@ -356,7 +356,7 @@ class StockBasicRow: ts_code=_required_text(row, "ts_code"), symbol=_required_text(row, "symbol"), name=_required_text(row, "name"), - market=_required_text(row, "market"), + market=_optional_text(row, "market"), exchange=_required_text(row, "exchange"), list_status=_required_text(row, "list_status"), list_date=_source_date(row, "list_date", required=False), diff --git a/zhixing-server/tests/unit/sector_radar/test_normalize.py b/zhixing-server/tests/unit/sector_radar/test_normalize.py index b639e49..a70db2c 100644 --- a/zhixing-server/tests/unit/sector_radar/test_normalize.py +++ b/zhixing-server/tests/unit/sector_radar/test_normalize.py @@ -36,12 +36,17 @@ def result[T](api_name: str, rows: tuple[T, ...]) -> SourceResult[T]: return SourceResult((snapshot,), rows) -def basic(ts_code: str, *, list_date: date = date(2020, 1, 1)) -> StockBasicRow: +def basic( + ts_code: str, + *, + list_date: date = date(2020, 1, 1), + market: str | None = "主板", +) -> StockBasicRow: return StockBasicRow( ts_code=ts_code, symbol=ts_code.split(".")[0], name=ts_code, - market="主板", + market=market, exchange="SZSE", list_status="L", list_date=list_date, @@ -49,6 +54,28 @@ def basic(ts_code: str, *, list_date: date = date(2020, 1, 1)) -> StockBasicRow: ) +def test_missing_market_keeps_hs_a_stock_but_code_rules_still_exclude_bse_and_b_shares() -> None: + codes = ("000001.SZ", "920001.BJ", "200001.SZ", "900001.SH") + basics = tuple(basic(code, market=None) for code in codes) + daily_rows = tuple(daily(code, Decimal("1")) for code in codes) + moneyflow_rows = tuple(moneyflow(code, Decimal("1")) for code in codes) + + facts = normalize_stock_facts( + target_trade_date=TARGET_DATE, + candidate_codes=codes, + stock_basics=result("stock_basic", basics), + suspensions=result("suspend_d", ()), + daily=result("daily", daily_rows), + moneyflow=result("moneyflow_dc", moneyflow_rows), + ) + by_code = {fact.ts_code: fact for fact in facts} + + assert by_code["000001.SZ"].status is StockFactStatus.AVAILABLE + assert by_code["920001.BJ"].status is StockFactStatus.LIFECYCLE_INVALID + assert by_code["200001.SZ"].status is StockFactStatus.LIFECYCLE_INVALID + assert by_code["900001.SH"].status is StockFactStatus.LIFECYCLE_INVALID + + def daily(ts_code: str, amount: Decimal | None) -> DailyRow: return DailyRow( ts_code=ts_code, diff --git a/zhixing-server/tests/unit/sector_radar/test_tushare_source.py b/zhixing-server/tests/unit/sector_radar/test_tushare_source.py index fdc0d5e..55bbc50 100644 --- a/zhixing-server/tests/unit/sector_radar/test_tushare_source.py +++ b/zhixing-server/tests/unit/sector_radar/test_tushare_source.py @@ -315,7 +315,7 @@ def test_stock_basic_explicitly_requests_all_lifecycle_statuses() -> None: "ts_code": f"00000{index}.SZ", "symbol": f"00000{index}", "name": status, - "market": "主板", + "market": None if status == "D" else "主板", "exchange": "SZSE", "list_status": status, "list_date": "20200101", @@ -329,6 +329,7 @@ def test_stock_basic_explicitly_requests_all_lifecycle_statuses() -> None: result = make_adapter(client).fetch_stock_basics() assert {row.list_status for row in result.rows} == {"L", "D", "P", "G", "UN"} + assert next(row for row in result.rows if row.list_status == "D").market is None assert [call[1]["list_status"] for call in client.calls] == ["L", "D", "P", "G", "UN"]