Tortoise/sidecar/service.py
2026-06-24 16:50:59 +08:00

117 lines
5.0 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.

"""统一数据获取与缓存服务。"""
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])