feat(quant): M7.3 研究行情口径显式化(默认不复权 none,可切 qfq)

- DailyBarRepository.get_range_many / stream_range_many_columns 增加 adjust 参数
  (默认 'none')→ SQL 层过滤口径,消除 stock_daily 混 source/adjust 污染因子的风险
- ResearchSpec / SelectionQuery 增加 price_adjustment(none|qfq),随 config_snapshot
  落库可溯源;ResearchService._load_daily 与 SelectionService 装配按口径取数
- tests/test_price_adjustment.py:repo 读取按 adjust 过滤(none/qfq 各自命中)、
  spec 默认与字段记录;全量 pytest 通过
This commit is contained in:
Simon
2026-09-09 00:32:55 +08:00
parent 4fa2bb748e
commit ef09d5b419
12 changed files with 153 additions and 13 deletions
+11 -3
View File
@@ -75,6 +75,7 @@ def load_daily_df(
start: date,
end: date,
columns: list[str],
adjust: str = "none",
) -> pd.DataFrame:
"""从 Repository 装配行情长表(供研究/选股共用)。
@@ -86,12 +87,14 @@ def load_daily_df(
streamer = getattr(daily_repo, "stream_range_many_columns", None)
if streamer is not None:
try:
return _frame_from_stream(streamer(symbols, start, end, sorted(columns)), sorted(columns))
return _frame_from_stream(
streamer(symbols, start, end, sorted(columns), adjust=adjust), sorted(columns)
)
except Exception: # noqa: BLE001 —— 流式路径失败回退旧路径(兼容非 SQL 实现)
pass
get_many = getattr(daily_repo, "get_range_many", None)
if get_many is not None:
bars = list(get_many(symbols, start, end))
bars = list(get_many(symbols, start, end, adjust=adjust))
else: # 兜底:逐只查询
bars = []
for sym in symbols:
@@ -134,5 +137,10 @@ class ResearchService:
# 引擎所需列裁剪(LocalEngine 只取 close + 因子字段;Qlib 回测取全 OHLCV)
required = self._engine.required_columns(spec)
return load_daily_df(
self._daily_repo, [s.symbol for s in stocks], data_start, end, sorted(required)
self._daily_repo,
[s.symbol for s in stocks],
data_start,
end,
sorted(required),
adjust=spec.price_adjustment,
)