refactor(provider): TET Phase3老接口内部委托Fetcher——①四老方法(get_price/get_closes_panel/get_index_stocks/get_fundamentals_df)旧体删除,改调同款Fetcher(与_ex两张皮,strict契约自此对老接口生效:非法frequency/fq/count/日期/空列表→ValueError,查询失败raise;Phase2副本对照4/4语义等值已验issue#19)②qfq因子二次读库归一:_build_qfq_factor/_apply_qfq_batch拆为_read_qfq_rows+_qfq_factor_from_rows与_read_qfq_events+_apply_qfq_events(IO/纯计算),因子读全挪进extract_data,transform零IO③all_weather四处get_fundamentals_df(choice)补空守卫(空候选集=调用方业务态)④3个宽松断言回归测试改strict(1m频率/空stocks/空symbols);938绿 [vps]
CI/CD / test (push) Successful in 14s
CI/CD / nas-deploy (push) Successful in 33s
CI/CD / nas-verify (push) Successful in 17s

This commit is contained in:
2026-08-17 09:12:47 +08:00
parent 5211d5a6ed
commit d2364a36c5
6 changed files with 163 additions and 301 deletions
+22 -14
View File
@@ -3,7 +3,7 @@
Mac 本地 TDD: sqlite tmp_path + tmp parquet fixture,零 VPS 依赖,零网络。
覆盖:
- ``jq_to_dbbardata`` / ``dbbardata_to_jq`` / ``_jq_to_bs_code`` 代码转换
- ``_build_qfq_factor`` 复权因子构造(asof)
- ``_build_qfq_factor`` 复权因子构造(asof;Phase 3 拆为 _read_qfq_rows IO + _qfq_factor_from_rows 纯计算)
- ``get_price`` dbbardata('d') raw + fq='qfq' 前复权 + panel=False 长表
- ``get_index_stocks`` constituent_unified 并集(治偏差,无 date 时点)
- ``get_fundamentals_df`` pe/pb/ps/pcf←valuation_baostock + 市值←static akshare + 三表委托 LocalParquetProvider
@@ -21,11 +21,17 @@ from sanguo_portfolio.providers.local_unified_provider import (
jq_to_dbbardata,
dbbardata_to_jq,
_jq_to_bs_code,
_build_qfq_factor,
_read_qfq_rows,
_qfq_factor_from_rows,
LocalUnifiedProvider,
)
def _build_qfq_factor(bs_code, conn, dates):
"""Phase 3 拆分后的组合形式(IO 读 + 纯计算),保留原测试调用形状。"""
return _qfq_factor_from_rows(_read_qfq_rows(bs_code, conn), dates)
# ======================== Task 0: 代码转换 ========================
class TestCodeFormat:
def test_jq_to_dbbardata_sh(self):
@@ -242,16 +248,16 @@ class TestGetPrice:
assert abs(df.iloc[0]["high_limit"] - expected) < 1e-6
def test_minute_frequency_returns_empty(self, unified_provider):
# 1m 频率无数据 → 返空 DataFrame
df = unified_provider.get_price(
"600519.XSHG",
end_date="2024-06-20",
frequency="1m",
count=1,
panel=False,
)
assert isinstance(df, pd.DataFrame)
assert df.empty
# Phase 3(2026-08-17)起老接口=Fetcher strict 契约:
# 1m 频率 → ValueError fail-fast(老实现静默返空表;15m 走 get_closes_panel)
with pytest.raises(ValueError, match="frequency"):
unified_provider.get_price(
"600519.XSHG",
end_date="2024-06-20",
frequency="1m",
count=1,
panel=False,
)
def test_multi_stocks_panel_false(self, tmp_path):
# 多股 panel=False → 长表含 code 列区分
@@ -469,10 +475,12 @@ class TestGetFundamentals:
assert col in df.columns, f"missing col: {col}"
def test_empty_stocks_returns_empty(self, tmp_path):
# Phase 3(2026-08-17)起老接口=Fetcher strict 契约:
# 空 stocks → ValueError(老实现返空表);空候选集由调用方守卫(if not choice)
db = _make_fundamentals_fixture(tmp_path)
p = LocalUnifiedProvider({"db_path": str(db), "data_dir": str(tmp_path)})
df = p.get_fundamentals_df([], date="2024-09-30")
assert df.empty
with pytest.raises(ValueError, match="stocks"):
p.get_fundamentals_df([], date="2024-09-30")
# ======================== Task 3b: get_fundamentals_df fields= 按需短路 + 并发 ========================
+4 -4
View File
@@ -183,10 +183,10 @@ class TestDateRangeAndInterval:
# ======================== 4. 空 symbols + dedup ========================
class TestEmptyAndDedup:
def test_empty_symbols_returns_empty_dataframe(self, batch_provider):
df = batch_provider.get_closes_panel([], start="2024-06-18", end="2024-06-20")
assert isinstance(df, pd.DataFrame)
assert df.empty
assert list(df.columns) == []
# Phase 3(2026-08-17)起老接口=Fetcher strict 契约:
# 空 symbols → ValueError(老实现返空骨架);空候选集由调用方守卫(if not stocks)
with pytest.raises(ValueError, match="symbols"):
batch_provider.get_closes_panel([], start="2024-06-18", end="2024-06-20")
def test_duplicate_rows_deduped_keep_last(self, tmp_path):
# 同 (symbol, datetime) 两行 close 不同 → dedup keep last