Files
sanguo_vnpy_v2/sanguo_trader/data_source.py
T
claude_dev c6b19f4244 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通过
2026-07-08 07:21:33 +08:00

85 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模拟盘行情统一接口(raw 真实价 / qfq 前复权 双源,spec §3.3 / §5)。
双源(分红除权准确方案):
- raw:撮合/涨跌停/成交价用真实价(adjustflag=3 / akshare adjust=""),cfg.data_paths['raw_dir']
- qfq:策略信号用前复权(无除权缺口,MA 信号准),cfg.data_paths['qfq_dir']
- daily_dir 是历史 mixedhfq+raw),仅 backtest 兼容,模拟盘不用
_read_fn 内 lazy import datareader,避免模块级依赖 vnpy 链(tzlocal 等),
本机无 vnpy 完整依赖时仍可 import + 单测(mock _read_fn)。
"""
import logging
from collections.abc import Iterator
logger = logging.getLogger(__name__)
def _read_fn(interval: str):
"""按 interval 返回读取函数(lazy import)。"""
if interval == "d":
from sanguo_data.datareader import read_parquet_daily
return read_parquet_daily
if interval == "15m":
from sanguo_data.datareader import read_parquet_15min
return read_parquet_15min
raise ValueError(f"不支持的 interval: {interval}(首版仅 d / 15m")
def _resolve_dir_key(adjust: str, interval: str) -> str:
"""adjust → cfg.data_paths 的目录 key。
raw 仅日线(raw 15min 待分期项);qfq/默认按 interval 选。
"""
if adjust == "raw":
if interval != "d":
raise ValueError(
f"raw 模式暂仅支持日线(interval='d'),got '{interval}'"
"raw 15min 待分期项)"
)
return "raw_dir"
if adjust == "qfq":
return "qfq_dir" # 干净 qfqdaily_dir 是 mixed,仅 backtest 兼容)
return "daily_dir" if interval == "d" else "minute_15_dir"
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(
f"{adjust} 模式需 cfg.data_paths['{need}'](未配置;"
f"先用 raw_redownload.py --adjust {adjust} 生成 parquet"
)
def iter_bars(
symbols: list[str],
start: str,
end: str,
interval: str,
adjust: str = "qfq",
cfg=None,
) -> Iterator[tuple]:
"""按日期 cross-section yield (date, {symbol: BarData})。"""
dir_key = _resolve_dir_key(adjust, interval)
_check_adjust_cfg(adjust, cfg)
read_fn = _read_fn(interval)
by_date: dict = {}
for sym in symbols:
for bar in read_fn(sym, start, end, cfg, dir_key):
dt = bar.datetime
key = dt.date() if hasattr(dt, "date") else dt
by_date.setdefault(key, {})[sym] = bar
for date in sorted(by_date.keys()):
yield date, by_date[date]
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_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