feat(data): B1-2 指数成分同步(Provider + CLI sync index_weight)

- MarketDataProvider.get_index_weight(协议);Tushare 实现 normalize_index_weight +
  get_index_weight(ts_code=... 全历史成分权重);Sina 抛 DataSourceNotSupported;
  FailoverProvider 代理并审计每次尝试
- CLI:sync index_weight --code 000300.SH(拉取→幂等落库 index_weight→打印最新快照;
  失败走 sync_log 审计并返回非零)
- tests:Tushare 映射与调用(FakePro)、Failover 主源单源语义(新浪不支持被审计);
  全量 pytest 通过
This commit is contained in:
Simon
2026-09-09 07:29:26 +08:00
parent 9cc4bfccac
commit 93e32f4e63
7 changed files with 140 additions and 0 deletions
+33
View File
@@ -91,3 +91,36 @@ class TestFailover:
with pytest.raises(DataSourceError, match="无备用源"):
f.get_daily("600519.SH", date(2024, 1, 1), date(2024, 1, 5))
assert len(collected) == 1
class TestIndexFailover:
def test_primary_only_index(self) -> None:
"""指数成分:主源成功即用(新浪不支持会自动尝试并审计失败 → 结果仍来自主源)。"""
from datetime import date
from decimal import Decimal
from app.domain.entities.index import IndexWeight
from app.infrastructure.data_sources.failover import FailoverProvider
from app.infrastructure.data_sources.sina import SinaProvider
from app.infrastructure.data_sources.tushare import TushareProvider
class _P(TushareProvider):
name = "tushare"
def __init__(self): # 不触网
pass
def get_index_weight(self, index_code):
return [IndexWeight(index_code=index_code, index_name="沪深300",
trade_date=date(2024, 6, 28), symbol="600519.SH",
weight=Decimal("1"))]
logs: list[SyncLog] = []
def audit(log: SyncLog):
logs.append(log)
f = FailoverProvider(_P(), SinaProvider(), audit=audit)
rows = f.get_index_weight("000300.SH")
assert len(rows) == 1 and rows[0].symbol == "600519.SH"
assert len(logs) >= 1 # 每次尝试已审计
+33
View File
@@ -224,3 +224,36 @@ class TestGetFinancialWindow:
assert len(fake.kwargs) == 2
assert fake.kwargs[1]["end_date"] < fake.kwargs[0]["end_date"]
assert {r.report_date for r in rows} == {date.fromisoformat(e[:4] + "-" + e[4:6] + "-" + e[6:]) for e in newest + older}
class TestIndexWeight:
def test_normalize_mapping(self) -> None:
from app.infrastructure.data_sources.tushare import TushareProvider
rows = TushareProvider.normalize_index_weight(
[
{"index_code": "000300.SH", "con_code": "600519.SH",
"trade_date": "20240628", "weight": 1.53},
{"con_code": "000001.SZ", "trade_date": "20240628", "weight": 0.9},
],
index_code_fallback="000300.SH",
)
assert len(rows) == 2
assert rows[0].index_code == "000300.SH"
assert rows[0].symbol == "600519.SH"
assert rows[0].trade_date.isoformat() == "2024-06-28"
assert float(rows[0].weight) == 1.53
# 无 index_code 时用 fallback;con_code 缺失跳过
assert rows[1].index_code == "000300.SH"
def test_provider_calls_index_weight(self) -> None:
from app.infrastructure.data_sources.tushare import TushareProvider
pro = FakePro(
payload=[{"index_code": "000300.SH", "con_code": "600519.SH",
"trade_date": "20240628", "weight": 1.0}]
)
p = TushareProvider(token="t", pro=pro)
rows = p.get_index_weight("000300.SH")
assert pro.calls == ["index_weight"]
assert len(rows) == 1 and rows[0].symbol == "600519.SH"