feat(data): 恢复双源(task#79)—撮合raw+策略qfq, 分红除权准确
用户要模拟=回测准确: raw除权缺口致MA假信号, 必须双源。
- data_source: qfq→qfq_dir(干净qfq), raw→raw_dir; _check_adjust_cfg(cfg提供才校验)
- engine 双bar流: step(raw_bars,qfq_bars)撮合/盯市raw+策略on_bar qfq; run zip(raw,qfq)
- live_orchestrator: warmup用qfq(信号am); 去adjust参数(双源固定)
- raw_redownload --adjust(''raw/'qfq'); config qfq_dir
- 113/113通过
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
"""模拟盘行情统一接口(raw 真实价 / qfq 前复权,spec §3.3 / §5)。
|
||||
"""模拟盘行情统一接口(raw 真实价 / qfq 前复权 双源,spec §3.3 / §5)。
|
||||
|
||||
raw:撮合/涨跌停/成交价用真实价(adjustflag=3 / akshare adjust=""),
|
||||
从 cfg.data_paths['raw_dir'] 读 parquet(raw 重下脚本生成,单一 adjust 不混源)。
|
||||
qfq:策略信号用前复权(无除权缺口),从 cfg.data_paths['daily_dir'] 读。
|
||||
双源(分红除权准确方案):
|
||||
- raw:撮合/涨跌停/成交价用真实价(adjustflag=3 / akshare adjust=""),cfg.data_paths['raw_dir']
|
||||
- qfq:策略信号用前复权(无除权缺口,MA 信号准),cfg.data_paths['qfq_dir']
|
||||
- daily_dir 是历史 mixed(hfq+raw),仅 backtest 兼容,模拟盘不用
|
||||
|
||||
_read_fn 内 lazy import datareader,避免模块级依赖 vnpy 链(tzlocal 等),
|
||||
本机无 vnpy 完整依赖时仍可 import + 单测(mock _read_fn)。
|
||||
@@ -27,7 +28,7 @@ def _read_fn(interval: str):
|
||||
def _resolve_dir_key(adjust: str, interval: str) -> str:
|
||||
"""adjust → cfg.data_paths 的目录 key。
|
||||
|
||||
raw 仅支持日线(raw 15min 待分红除权分期项);qfq/默认按 interval 选日线/15min。
|
||||
raw 仅日线(raw 15min 待分期项);qfq/默认按 interval 选。
|
||||
"""
|
||||
if adjust == "raw":
|
||||
if interval != "d":
|
||||
@@ -36,15 +37,18 @@ def _resolve_dir_key(adjust: str, interval: str) -> str:
|
||||
"(raw 15min 待分期项)"
|
||||
)
|
||||
return "raw_dir"
|
||||
if adjust == "qfq":
|
||||
return "qfq_dir" # 干净 qfq(daily_dir 是 mixed,仅 backtest 兼容)
|
||||
return "daily_dir" if interval == "d" else "minute_15_dir"
|
||||
|
||||
|
||||
def _check_raw_cfg(adjust: str, cfg) -> None:
|
||||
"""raw 模式需 raw_dir 配置,缺失明确报错(不静默 fallback 到 qfq,避免混源)。"""
|
||||
if adjust == "raw" and (not cfg or "raw_dir" not in getattr(cfg, "data_paths", {})):
|
||||
def _check_adjust_cfg(adjust: str, cfg) -> None:
|
||||
"""raw/qfq 需对应 dir 配置,缺失明确报错(不静默 fallback,避免混源)。"""
|
||||
need = {"raw": "raw_dir", "qfq": "qfq_dir"}.get(adjust)
|
||||
if need and cfg and need not in getattr(cfg, "data_paths", {}):
|
||||
raise ValueError(
|
||||
"raw 模式需 cfg.data_paths['raw_dir'](未配置;"
|
||||
"先用 scripts/data_platform/raw_redownload.py 生成 raw parquet)"
|
||||
f"{adjust} 模式需 cfg.data_paths['{need}'](未配置;"
|
||||
f"先用 raw_redownload.py --adjust {adjust} 生成 parquet)"
|
||||
)
|
||||
|
||||
|
||||
@@ -58,7 +62,7 @@ def iter_bars(
|
||||
) -> Iterator[tuple]:
|
||||
"""按日期 cross-section yield (date, {symbol: BarData})。"""
|
||||
dir_key = _resolve_dir_key(adjust, interval)
|
||||
_check_raw_cfg(adjust, cfg)
|
||||
_check_adjust_cfg(adjust, cfg)
|
||||
read_fn = _read_fn(interval)
|
||||
by_date: dict = {}
|
||||
for sym in symbols:
|
||||
@@ -74,7 +78,7 @@ def fetch_day(symbol: str, date: str, interval: str,
|
||||
adjust: str = "qfq", cfg=None):
|
||||
"""实走模式拉当日 bar(C-S3 用)。"""
|
||||
dir_key = _resolve_dir_key(adjust, interval)
|
||||
_check_raw_cfg(adjust, cfg)
|
||||
_check_adjust_cfg(adjust, cfg)
|
||||
read_fn = _read_fn(interval)
|
||||
bars = read_fn(symbol, date, date, cfg, dir_key)
|
||||
return bars[-1] if bars else None
|
||||
|
||||
Reference in New Issue
Block a user