from __future__ import annotations from datetime import datetime import akshare as ak import pandas as pd from market.provider_base import MarketDataProvider class AkshareCnProvider(MarketDataProvider): @property def provider_name(self) -> str: return "akshare" @property def channel(self) -> str: return "cn" def fetch_spot(self) -> pd.DataFrame: df = ak.stock_zh_a_spot_em() if df.empty: raise RuntimeError("未获取到 A 股实时行情数据") return df def search_spot(self, query: str, limit: int) -> pd.DataFrame: q = query.strip().lower() if not q: return pd.DataFrame() df = self.fetch_spot() code = df["代码"].astype(str).str.lower() name = df["名称"].astype(str).str.lower() exact = df[(code == q) | (name == q)] if not exact.empty: return exact.head(limit) starts = df[code.str.startswith(q) | name.str.startswith(q)] if not starts.empty: return starts.head(limit) return df[code.str.contains(q, na=False) | name.str.contains(q, na=False)].head(limit) def fetch_daily_kline(self, code: str, start: datetime, end: datetime) -> pd.DataFrame: hist = ak.stock_zh_a_hist( symbol=code, period="daily", start_date=start.strftime("%Y%m%d"), end_date=end.strftime("%Y%m%d"), adjust="qfq", ) if hist.empty: raise RuntimeError(f"未获取到 K 线数据: {code}") frame = pd.DataFrame( { "date": pd.to_datetime(hist["日期"]), "close": pd.to_numeric(hist["收盘"], errors="coerce"), "volume": pd.to_numeric(hist["成交量"], errors="coerce"), } ).dropna() return frame.sort_values("date").reset_index(drop=True)