fix(provider): SanguoMiniQmtProvider四缺口根治——实盘/影子momentum/small_cap共4账户8-18/8-19空转两日——①get_closes_panel(+_ex)委托本地dbbardata(与回测LocalUnifiedProvider同源同口径;base与xtdata SDK均无此方法,bullet_trade回退链终断AttributeError,momentum牛熊/small_cap动量瘫)②get_fundamentals_df补fields契约(keep=code+请求列,对齐unified;原small_cap fields=[market_cap,eps]直接TypeError)+_ex别名③get_constituent_ex(+get_index_stocks覆盖)委托constituent_unified并集(历史日期口径,治前视;表缺指数WARNING回退base最新成份,宁可降级不空转=本次事故教训)④config的db_path/data_dir透传统一provider,VPS缺省走生产库;+10测试(复刻VPS AttributeError实锤+回退路径);962绿;VPS实库冒烟panel新鲜到08-18/000985=5128只(前后端session巡检实锤清单全闭环) [vps]
This commit is contained in:
@@ -141,3 +141,134 @@ class TestSetDataProviderInjection:
|
||||
active = get_data_provider()
|
||||
assert active is provider
|
||||
assert active.name == "sanguo_miniqmt"
|
||||
|
||||
|
||||
# ======================== 本地库委托(2026-08-19 生产缺口修复) ========================
|
||||
# VPS 实盘/影子 8-18/8-19 连续两天空转根因:SanguoMiniQmtProvider 缺
|
||||
# get_closes_panel/get_constituent_ex + get_fundamentals_df 不认 fields +
|
||||
# get_index_stocks 忽略历史日期(前后端 session 巡检实锤,见 memory
|
||||
# data-session-todo-miniqmt-provider-gaps)。修法:内部持有 LocalUnifiedProvider
|
||||
# 读本地 dbbardata/constituent_unified(与回测同口径)。
|
||||
def _make_delegate_db(tmp_path) -> str:
|
||||
"""dbbardata + constituent_unified 小样本库(委托 LocalUnifiedProvider 用)。"""
|
||||
import sqlite3
|
||||
|
||||
db = tmp_path / "delegate.db"
|
||||
c = sqlite3.connect(str(db))
|
||||
c.execute(
|
||||
"CREATE TABLE dbbardata(symbol TEXT, exchange TEXT, datetime TEXT, "
|
||||
"interval TEXT, volume REAL, turnover REAL, open_interest REAL, "
|
||||
"open_price REAL, high_price REAL, low_price REAL, close_price REAL)"
|
||||
)
|
||||
for i, dt in enumerate(["2024-06-18 00:00:00", "2024-06-19 00:00:00",
|
||||
"2024-06-20 00:00:00"]):
|
||||
c.execute(
|
||||
"INSERT INTO dbbardata VALUES(?,?,?,?,?,?,?,?,?,?,?)",
|
||||
("600519", "SSE", dt, "d", 1000.0, 1e6, 0,
|
||||
1000.0 + i, 1005.0 + i, 995.0 + i, 1000.0 + i),
|
||||
)
|
||||
c.execute(
|
||||
"CREATE TABLE constituent_unified(index_code TEXT, code TEXT, "
|
||||
"code_name TEXT, source TEXT, in_current INT, was_removed INT)"
|
||||
)
|
||||
c.executemany(
|
||||
"INSERT INTO constituent_unified VALUES(?,?,?,?,?,?)",
|
||||
[
|
||||
("000985", "600519", "贵州茅台", "csindex", 1, 0),
|
||||
("000985", "000858", "五粮液", "csindex", 0, 1), # 被踢也在并集
|
||||
],
|
||||
)
|
||||
c.commit()
|
||||
c.close()
|
||||
return str(db)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def delegate_provider(mock_xtquant, tmp_path):
|
||||
"""配好本地库路径的 SanguoMiniQmtProvider(db_path/data_dir 透传统一 provider)。"""
|
||||
db = _make_delegate_db(tmp_path)
|
||||
return SanguoMiniQmtProvider({
|
||||
"db_path": db, "data_dir": str(tmp_path), "auto_download": False,
|
||||
})
|
||||
|
||||
|
||||
class TestGetClosesPanelDelegation:
|
||||
"""get_closes_panel/get_closes_panel_ex 委托本地 dbbardata(同回测口径)。"""
|
||||
|
||||
def test_returns_wide_table_from_local_db(self, delegate_provider):
|
||||
panel = delegate_provider.get_closes_panel(
|
||||
["600519.XSHG"], "2024-06-18", "2024-06-20", fq="raw",
|
||||
)
|
||||
assert isinstance(panel, pd.DataFrame)
|
||||
assert list(panel.columns) == ["600519.XSHG"]
|
||||
assert len(panel) == 3
|
||||
assert abs(panel["600519.XSHG"].iloc[0] - 1000.0) < 1e-6
|
||||
assert abs(panel["600519.XSHG"].iloc[-1] - 1002.0) < 1e-6
|
||||
|
||||
def test_ex_alias_returns_same_result(self, delegate_provider):
|
||||
old = delegate_provider.get_closes_panel(
|
||||
["600519.XSHG"], "2024-06-18", "2024-06-20", fq="raw",
|
||||
)
|
||||
ex = delegate_provider.get_closes_panel_ex(
|
||||
["600519.XSHG"], "2024-06-18", "2024-06-20", fq="raw",
|
||||
)
|
||||
pd.testing.assert_frame_equal(old, ex)
|
||||
|
||||
def test_missing_symbol_returns_nan_column(self, delegate_provider):
|
||||
panel = delegate_provider.get_closes_panel(
|
||||
["600519.XSHG", "999999.XSHG"], "2024-06-18", "2024-06-20",
|
||||
)
|
||||
assert list(panel.columns) == ["600519.XSHG", "999999.XSHG"]
|
||||
assert panel["999999.XSHG"].isna().all()
|
||||
|
||||
def test_pure_digit_codes_accepted(self, delegate_provider):
|
||||
panel = delegate_provider.get_closes_panel(
|
||||
["600519"], "2024-06-18", "2024-06-20",
|
||||
)
|
||||
assert abs(panel["600519"].iloc[-1] - 1002.0) < 1e-6
|
||||
|
||||
|
||||
class TestGetIndexStocksDelegation:
|
||||
"""get_index_stocks/get_constituent_ex 委托 constituent_unified(支持历史日期口径)。"""
|
||||
|
||||
def test_reads_constituent_unified_union(self, delegate_provider):
|
||||
stocks = delegate_provider.get_index_stocks("000985.XSHG", "2024-06-19")
|
||||
# 并集语义:在册 + 被踢(was_removed)都返回
|
||||
assert set(stocks) == {"600519.XSHG", "000858.XSHE"}
|
||||
|
||||
def test_constituent_ex_delegates_same(self, delegate_provider):
|
||||
old = delegate_provider.get_index_stocks("000985.XSHG", "2024-06-19")
|
||||
ex = delegate_provider.get_constituent_ex("000985.XSHG", "2024-06-19")
|
||||
assert old == ex
|
||||
|
||||
def test_unknown_index_falls_back_to_xt_latest(self, delegate_provider, mock_xtquant, caplog):
|
||||
"""表里没有的指数 → WARNING + 回退 miniQMT 最新成分(宁可降级不空转)。"""
|
||||
mock_xtquant["xtdata"].get_index_weight.return_value = {"600519.SH": 0.5}
|
||||
with caplog.at_level("WARNING", logger="sanguo_portfolio.providers.sanguo_fundamentals"):
|
||||
stocks = delegate_provider.get_index_stocks("399303.XSHE", "2024-06-19")
|
||||
assert stocks == ["600519.XSHG"]
|
||||
assert any("回退" in r.message for r in caplog.records)
|
||||
|
||||
|
||||
class TestGetFundamentalsDfFields:
|
||||
"""get_fundamentals_df 加 fields 契约(对齐 unified:keep = code + 请求列)。"""
|
||||
|
||||
def test_fields_filters_columns(self, mock_xtquant):
|
||||
provider = SanguoMiniQmtProvider({"mode": "backtest", "auto_download": False})
|
||||
df = provider.get_fundamentals_df(
|
||||
["600519.XSHG"], date="2024-09-30", fields=["market_cap", "eps"],
|
||||
)
|
||||
assert list(df.columns) == ["code", "market_cap", "eps"]
|
||||
assert 19000 < float(df.iloc[0]["market_cap"]) < 22000
|
||||
|
||||
def test_fields_none_keeps_all_columns(self, mock_xtquant):
|
||||
provider = SanguoMiniQmtProvider({"mode": "backtest", "auto_download": False})
|
||||
df = provider.get_fundamentals_df(["600519.XSHG"], date="2024-09-30")
|
||||
assert "roe" in df.columns # fields=None 全列(向后兼容)
|
||||
|
||||
def test_ex_alias_accepts_fields(self, mock_xtquant):
|
||||
provider = SanguoMiniQmtProvider({"mode": "backtest", "auto_download": False})
|
||||
df = provider.get_fundamentals_df_ex(
|
||||
["600519.XSHG"], date="2024-09-30", fields=["eps"],
|
||||
)
|
||||
assert list(df.columns) == ["code", "eps"]
|
||||
|
||||
Reference in New Issue
Block a user