"""统一数据获取与缓存服务。""" from __future__ import annotations from typing import List, Optional from sidecar.config import SidecarConfig from sidecar.models import KlineBar, Snapshot from sidecar.sources.base import MarketDataSource from sidecar.symbols import normalize_symbol from sidecar.storage import DuckDBStore class FallbackKlineSource: """主备 K 线数据源。""" def __init__(self, primary: MarketDataSource, fallback: MarketDataSource, primary_periods: List[str]) -> None: """ 功能说明:创建按周期选择主备源的 K 线适配器。 参数说明:primary 为主数据源,fallback 为备用数据源,primary_periods 为主数据源支持的周期列表。 返回值说明:无返回值。 注意事项:主源仅在支持周期内使用,主源异常时自动回退到备用源。 """ self.primary = primary self.fallback = fallback self.primary_periods = set(primary_periods) def fetch_kline(self, symbol: str, period: str, limit: int) -> List[KlineBar]: """ 功能说明:获取 K 线并在主源失败时回退备用源。 参数说明:symbol 为股票代码,period 为周期,limit 为最大条数。 返回值说明:返回统一 KlineBar 列表。 注意事项:不在主源支持周期内的数据直接使用备用源。 """ if period in self.primary_periods: try: return self.primary.fetch_kline(symbol, period, limit) except Exception: pass return self.fallback.fetch_kline(symbol, period, limit) class UnifiedDataService: """统一行情获取与缓存服务。""" def __init__( self, store: DuckDBStore, config: SidecarConfig, kline_source: MarketDataSource, snapshot_sources: List[MarketDataSource], ) -> None: """ 功能说明:创建统一数据服务。 参数说明:store 为 DuckDB 仓储,config 为配置,kline_source 为 K 线源,snapshot_sources 为快照源列表。 返回值说明:无返回值。 注意事项:快照源按列表顺序尝试,前一个失败会自动降级到下一个。 """ self.store = store self.config = config self.kline_source = kline_source self.snapshot_sources = snapshot_sources def get_kline(self, symbol: str, period: str = "day", limit: int = 800, refresh: bool = False) -> List[KlineBar]: """ 功能说明:获取统一 K 线数据并自动缓存。 参数说明:symbol 为股票代码,period 为周期,limit 为最大条数,refresh 表示是否强制刷新。 返回值说明:返回按日期升序排列的 KlineBar 列表。 注意事项:缓存数量达到 limit 且未强制刷新时直接返回本地数据。 """ normalized = normalize_symbol(symbol) if not refresh: cached = self.store.load_kline(normalized, period, limit) if len(cached) >= limit: return cached bars = self.kline_source.fetch_kline(normalized, period, limit) self.store.save_kline(bars) return self.store.load_kline(normalized, period, limit) def get_snapshot(self, symbol: str, refresh: bool = False) -> Snapshot: """ 功能说明:获取统一行情快照并自动缓存。 参数说明:symbol 为股票代码,refresh 表示是否强制刷新。 返回值说明:返回 Snapshot 快照。 注意事项:默认使用未过期缓存;远端失败时会尝试下一个数据源。 """ normalized = normalize_symbol(symbol) if not refresh: cached = self.store.load_snapshot(normalized) if cached is not None: return cached last_error = None for source in self.snapshot_sources: try: snapshot = source.fetch_snapshot(normalized) self.store.save_snapshot(snapshot, self.config.snapshot_ttl_seconds) return snapshot except Exception as exc: last_error = exc raise RuntimeError("所有快照数据源均失败: %s" % symbol) from last_error def create_default_service(config: SidecarConfig) -> UnifiedDataService: """ 功能说明:创建默认 sidecar 服务实例。 参数说明:config 为 sidecar 配置。 返回值说明:返回 UnifiedDataService。 注意事项:默认 K 线使用 mootdx,快照优先腾讯财经并回退到 mootdx。 """ from sidecar.sources.mootdx_source import MootdxSource from sidecar.sources.tencent import TencentFinanceSource store = DuckDBStore(config.db_path) mootdx = MootdxSource() tencent = TencentFinanceSource(config.request_timeout_seconds) kline_source = FallbackKlineSource(tencent, mootdx, ["day", "week", "month"]) return UnifiedDataService(store, config, kline_source, [tencent, mootdx])