Files
AITrading/python-app/app/market/providers/cmes_provider.py
2026-03-26 14:13:44 +08:00

71 lines
2.4 KiB
Python

from __future__ import annotations
import importlib
import os
from datetime import datetime
import pandas as pd
from market.provider_base import MarketDataProvider
class CmesCnProvider(MarketDataProvider):
def __init__(self, token: str | None = None) -> None:
self._token = token or os.getenv("CMES_TOKEN", "").strip()
self._module = importlib.import_module("cmesdata")
self._login_once()
@property
def provider_name(self) -> str:
return "cmesdata"
@property
def channel(self) -> str:
return "cn"
def _login_once(self) -> None:
if not self._token:
raise RuntimeError("CMES_TOKEN 未配置,无法使用 cmesdata 通道")
self._module.login(self._token)
@staticmethod
def _to_prefixed_code(code: str) -> str:
raw = code.strip().upper().replace(".", "")
if raw.startswith("SH") or raw.startswith("SZ"):
return f"{raw[:2]}.{raw[2:]}"
if raw.isdigit() and len(raw) == 6:
if raw.startswith(("6", "9")):
return f"SH.{raw}"
return f"SZ.{raw}"
raise RuntimeError("cmesdata 通道仅支持 6 位 A 股代码或 SH./SZ. 前缀代码")
def fetch_spot(self) -> pd.DataFrame:
raise RuntimeError("cmesdata 不支持全市场快照拉取,请通过精确代码查询")
def search_spot(self, query: str, limit: int) -> pd.DataFrame:
_ = limit
code = self._to_prefixed_code(query)
df = self._module.get_real_hq([code])
if df is None or df.empty:
raise RuntimeError(f"未获取到实时行情: {code}")
return df
def fetch_daily_kline(self, code: str, start: datetime, end: datetime) -> pd.DataFrame:
prefixed = self._to_prefixed_code(code)
df = self._module.get_history_data(
prefixed,
start.strftime("%Y-%m-%d"),
end.strftime("%Y-%m-%d"),
"D",
)
if df is None or df.empty:
raise RuntimeError(f"未获取到历史 K 线: {prefixed}")
frame = pd.DataFrame(
{
"date": pd.to_datetime(df["时间"]),
"close": pd.to_numeric(df["收盘价"], errors="coerce"),
"volume": pd.to_numeric(df["成交量"], errors="coerce"),
}
).dropna()
return frame.sort_values("date").reset_index(drop=True)