"""mootdx 数据源适配器。""" from __future__ import annotations from typing import Any, Dict, List from sidecar.models import KlineBar, Snapshot, parse_date, parse_float from sidecar.symbols import is_index_symbol, mootdx_market, normalize_symbol, split_symbol PERIOD_CATEGORY = { "day": 4, "week": 5, "month": 6, "1m": 7, "5m": 8, "15m": 9, "30m": 10, "60m": 11, } class MootdxSource: """mootdx 行情适配器。""" def __init__(self) -> None: """ 功能说明:创建 mootdx 数据源。 参数说明:无。 返回值说明:无返回值。 注意事项:mootdx 依赖在初始化时导入,未安装会抛出清晰异常。 """ try: from mootdx.quotes import Quotes except ImportError as exc: raise RuntimeError("请先安装 mootdx:pip install mootdx") from exc self.client = Quotes.factory(market="std") def fetch_kline(self, symbol: str, period: str, limit: int) -> List[KlineBar]: """ 功能说明:通过 mootdx 获取 K 线数据。 参数说明:symbol 为股票代码,period 为周期,limit 为最大条数。 返回值说明:返回统一 KlineBar 列表。 注意事项:period 支持 day/week/month/1m/5m/15m/30m/60m。 """ normalized = normalize_symbol(symbol) category = PERIOD_CATEGORY.get(period) if category is None: raise ValueError("不支持的 K 线周期: %s" % period) _, code = split_symbol(normalized) raw = self._call_bars(normalized, code, mootdx_market(normalized), category, limit) records = self._records(raw) bars = [] for item in records: trade_date = parse_date(item.get("datetime") or item.get("date") or item.get("time")) if trade_date is None: continue bars.append( KlineBar( symbol=normalized, period=period, trade_date=trade_date, open=parse_float(item.get("open")), high=parse_float(item.get("high")), low=parse_float(item.get("low")), close=parse_float(item.get("close")), volume=parse_float(item.get("vol") or item.get("volume")), amount=parse_float(item.get("amount")), source="mootdx", ) ) return bars[-limit:] def fetch_snapshot(self, symbol: str) -> Snapshot: """ 功能说明:通过 mootdx 获取实时行情快照。 参数说明:symbol 为股票代码。 返回值说明:返回统一 Snapshot 模型。 注意事项:mootdx 快照字段因版本可能不同,缺失字段统一返回 None。 """ normalized = normalize_symbol(symbol) _, code = split_symbol(normalized) raw = self.client.quotes(symbol=[code], market=mootdx_market(normalized)) records = self._records(raw) if not records: raise ValueError("mootdx 未返回快照: %s" % symbol) item = records[0] price = parse_float(item.get("price") or item.get("now") or item.get("last_close")) previous_close = parse_float(item.get("last_close") or item.get("pre_close")) return Snapshot( symbol=normalized, name=item.get("name"), trade_time=None, price=price, previous_close=previous_close, open=parse_float(item.get("open")), high=parse_float(item.get("high")), low=parse_float(item.get("low")), volume=parse_float(item.get("vol") or item.get("volume")), amount=parse_float(item.get("amount")), change=None if price is None or previous_close is None else price - previous_close, change_percent=None if price is None or previous_close in (None, 0) else (price - previous_close) / previous_close * 100, turnover_rate=parse_float(item.get("turnover")), pe_ttm=parse_float(item.get("pe_ttm") or item.get("pe")), pe_static=None, pb=parse_float(item.get("pb")), market_cap=None, float_market_cap=None, limit_up=None, limit_down=None, source="mootdx", ) def _call_bars(self, symbol: str, code: str, market: int, category: int, limit: int) -> Any: """ 功能说明:兼容调用不同版本 mootdx 的 K 线接口。 参数说明:symbol 为标准代码,code 为六位代码,market 为市场编号,category 为周期编号,limit 为数量。 返回值说明:返回 mootdx 原始结果对象。 注意事项:沪深指数必须使用 index_bars,普通 bars 会返回错误价格或异常日期。 """ method = self.client.index_bars if is_index_symbol(symbol) else self.client.bars try: return method(symbol=code, market=market, category=category, count=limit) except TypeError: return method(symbol=code, market=market, frequency=category, offset=limit) def _records(self, raw: Any) -> List[Dict[str, Any]]: """ 功能说明:把 mootdx 返回值转换为字典列表。 参数说明:raw 为 DataFrame、字典列表或其他可迭代对象。 返回值说明:返回普通 dict 列表。 注意事项:处理 datetime 同时存在于索引和列时的 reset_index 冲突。 """ if hasattr(raw, "to_dict"): data = raw if hasattr(data, "columns") and "datetime" in data.columns: data = data.copy() if hasattr(data, "reset_index"): try: data = data.reset_index() except ValueError: data = data.drop(columns=["datetime"]).reset_index() return data.to_dict("records") if isinstance(raw, list): return raw return list(raw or [])