save
This commit is contained in:
+31
-1
@@ -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])
|
||||
@@ -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]]:
|
||||
"""
|
||||
|
||||
@@ -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
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user