This commit is contained in:
cheney
2026-06-24 16:50:59 +08:00
parent db4e519cc2
commit e046c08e02
18 changed files with 386 additions and 34 deletions
+31 -1
View File
@@ -11,6 +11,35 @@ 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:
"""统一行情获取与缓存服务。"""
@@ -84,4 +113,5 @@ def create_default_service(config: SidecarConfig) -> UnifiedDataService:
store = DuckDBStore(config.db_path)
mootdx = MootdxSource()
tencent = TencentFinanceSource(config.request_timeout_seconds)
return UnifiedDataService(store, config, mootdx, [tencent, mootdx])
kline_source = FallbackKlineSource(tencent, mootdx, ["day", "week", "month"])
return UnifiedDataService(store, config, kline_source, [tencent, mootdx])
+9 -8
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from typing import Any, Dict, List
from sidecar.models import KlineBar, Snapshot, parse_date, parse_float
from sidecar.symbols import mootdx_market, normalize_symbol, split_symbol
from sidecar.symbols import is_index_symbol, mootdx_market, normalize_symbol, split_symbol
PERIOD_CATEGORY = {
@@ -48,7 +48,7 @@ class MootdxSource:
if category is None:
raise ValueError("不支持的 K 线周期: %s" % period)
_, code = split_symbol(normalized)
raw = self._call_bars(code, mootdx_market(normalized), category, limit)
raw = self._call_bars(normalized, code, mootdx_market(normalized), category, limit)
records = self._records(raw)
bars = []
for item in records:
@@ -111,17 +111,18 @@ class MootdxSource:
source="mootdx",
)
def _call_bars(self, code: str, market: int, category: int, limit: int) -> Any:
def _call_bars(self, symbol: str, code: str, market: int, category: int, limit: int) -> Any:
"""
功能说明:兼容调用不同版本 mootdx 的 bars 接口。
参数说明:code 为六位股票代码,market 为市场编号,category 为周期编号,limit 为数量。
功能说明:兼容调用不同版本 mootdx 的 K 线接口。
参数说明:symbol 为标准代码,code 为六位代码,market 为市场编号,category 为周期编号,limit 为数量。
返回值说明:返回 mootdx 原始结果对象。
注意事项:不同 mootdx 版本参数名存在差异,因此保留一次兼容重试。
注意事项:沪深指数必须使用 index_bars,普通 bars 会返回错误价格或异常日期。
"""
method = self.client.index_bars if is_index_symbol(symbol) else self.client.bars
try:
return self.client.bars(symbol=code, market=market, category=category, count=limit)
return method(symbol=code, market=market, category=category, count=limit)
except TypeError:
return self.client.bars(symbol=code, market=market, frequency=category, offset=0)
return method(symbol=code, market=market, frequency=category, offset=limit)
def _records(self, raw: Any) -> List[Dict[str, Any]]:
"""
+2 -2
View File
@@ -36,8 +36,8 @@ class TencentFinanceSource:
if tencent_period is None:
raise ValueError("腾讯财经 K 线暂不支持周期: %s" % period)
normalized = normalize_symbol(symbol)
url = "https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?" + urlencode(
{"param": "%s,%s,,,%d,qfq" % (normalized, tencent_period, limit)}
url = "https://web.ifzq.gtimg.cn/appstock/app/kline/kline?" + urlencode(
{"param": "%s,%s,,,%d" % (normalized, tencent_period, limit)}
)
request = Request(url, headers={"User-Agent": "Mozilla/5.0 TortoiseSidecar/0.1"})
with urlopen(request, timeout=self.timeout_seconds) as response:
+12 -1
View File
@@ -44,4 +44,15 @@ def mootdx_market(symbol: str) -> int:
注意事项:mootdx 对北交所支持随版本变化,这里按通达信常见约定归入 0。
"""
market, _ = split_symbol(symbol)
return 1 if market == "sh" else 0
return 1 if market == "sh" else 0
def is_index_symbol(symbol: str) -> bool:
"""
功能说明:判断代码是否为常见沪深指数代码。
参数说明:symbol 为任意支持格式的股票或指数代码。
返回值说明:指数代码返回 True,其他代码返回 False。
注意事项:仅按沪市 000、深市 399 前缀识别,避免影响普通股票 K 线。
"""
market, code = split_symbol(symbol)
return (market == "sh" and code.startswith("000")) or (market == "sz" and code.startswith("399"))