"""模拟盘行情统一接口(qfq/raw 双源,spec §3.3 / §5)。 _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 iter_bars( symbols: list[str], start: str, end: str, interval: str, adjust: str = "qfq", cfg=None, ) -> Iterator[tuple]: """按日期 cross-section yield (date, {symbol: BarData})。 raw 首版 fallback qfq + warning(NAS 暂无 raw parquet,spec §17 开放项)。 """ if adjust == "raw": logger.warning( "raw 模式首版 fallback qfq(NAS 暂无 raw parquet,spec §17 开放项)" ) read_fn = _read_fn(interval) by_date: dict = {} for sym in symbols: for bar in read_fn(sym, start, end, cfg): 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 用)。""" if adjust == "raw": logger.warning("raw fallback qfq(spec §17)") read_fn = _read_fn(interval) bars = read_fn(symbol, date, date, cfg) return bars[-1] if bars else None