fix(sector-radar): 兼容股票市场字段为空

This commit is contained in:
yuxuanhui
2026-08-30 01:50:27 +08:00
parent 6e217ac70f
commit 1e15e8059d
4 changed files with 35 additions and 6 deletions
@@ -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
@@ -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),
@@ -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,
@@ -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"]