|
|
"""
|
|
|
AmazingData SDK适配器
|
|
|
封装AmazingData SDK的所有接口
|
|
|
"""
|
|
|
import logging
|
|
|
from typing import List, Dict, Callable, Optional, Tuple
|
|
|
from datetime import date
|
|
|
import pandas as pd
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
class AmazingDataAdapter:
|
|
|
"""AmazingData SDK适配器"""
|
|
|
|
|
|
def __init__(self, config: Dict):
|
|
|
"""
|
|
|
初始化适配器
|
|
|
|
|
|
Args:
|
|
|
config: SDK配置字典
|
|
|
- username: 用户名
|
|
|
- password: 密码
|
|
|
- host: 服务器地址
|
|
|
- port: 端口
|
|
|
- local_path: 本地缓存路径
|
|
|
"""
|
|
|
self.config = config
|
|
|
self._ad = None
|
|
|
self._base_data = None
|
|
|
self._market_data = None
|
|
|
self._info_data = None
|
|
|
self._calendar = None
|
|
|
self._is_connected = False
|
|
|
|
|
|
def connect(self) -> bool:
|
|
|
"""
|
|
|
建立与AmazingData SDK的连接
|
|
|
|
|
|
Returns:
|
|
|
是否连接成功
|
|
|
"""
|
|
|
try:
|
|
|
import AmazingData as ad
|
|
|
|
|
|
ad.login(
|
|
|
username=self.config["username"],
|
|
|
password=self.config["password"],
|
|
|
host=self.config["host"],
|
|
|
port=self.config["port"]
|
|
|
)
|
|
|
|
|
|
self._ad = ad
|
|
|
|
|
|
try:
|
|
|
self._base_data = ad.BaseData()
|
|
|
except Exception as e:
|
|
|
logger.error(f"AmazingData SDK登录验证失败: {str(e)}")
|
|
|
return False
|
|
|
|
|
|
self._info_data = ad.InfoData()
|
|
|
self._calendar = self._base_data.get_calendar()
|
|
|
self._market_data = ad.MarketData(self._calendar)
|
|
|
|
|
|
self._is_connected = True
|
|
|
logger.info("AmazingData SDK连接成功")
|
|
|
return True
|
|
|
|
|
|
except ImportError:
|
|
|
logger.error("AmazingData SDK未安装,请安装SDK后再试")
|
|
|
return False
|
|
|
except Exception as e:
|
|
|
logger.error(f"AmazingData SDK连接失败: {str(e)}")
|
|
|
return False
|
|
|
|
|
|
def disconnect(self):
|
|
|
"""断开SDK连接"""
|
|
|
if self._is_connected and self._ad:
|
|
|
try:
|
|
|
self._ad.logout(self.config["username"])
|
|
|
except Exception as e:
|
|
|
logger.warning(f"断开SDK连接时出错: {str(e)}")
|
|
|
|
|
|
self._is_connected = False
|
|
|
self._ad = None
|
|
|
self._base_data = None
|
|
|
self._market_data = None
|
|
|
self._info_data = None
|
|
|
self._calendar = None
|
|
|
logger.info("AmazingData SDK已断开")
|
|
|
|
|
|
def is_connected(self) -> bool:
|
|
|
"""检查是否已连接"""
|
|
|
return self._is_connected
|
|
|
|
|
|
# ==================== 基础数据接口 ====================
|
|
|
|
|
|
def get_code_list(self, security_type: str) -> List[str]:
|
|
|
"""
|
|
|
获取代码列表
|
|
|
|
|
|
Args:
|
|
|
security_type: 证券类型
|
|
|
- EXTRA_STOCK_A: 沪深A股
|
|
|
- EXTRA_FUTURE: 期货
|
|
|
- EXTRA_ETF: ETF
|
|
|
- EXTRA_INDEX_A: 指数
|
|
|
|
|
|
Returns:
|
|
|
代码列表
|
|
|
"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
return self._base_data.get_code_list(security_type)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取代码列表失败: {str(e)}")
|
|
|
return []
|
|
|
|
|
|
def get_code_info(self, security_type: str) -> pd.DataFrame:
|
|
|
"""
|
|
|
获取证券信息
|
|
|
|
|
|
Args:
|
|
|
security_type: 证券类型
|
|
|
|
|
|
Returns:
|
|
|
证券信息DataFrame
|
|
|
"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
return self._base_data.get_code_info(security_type)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取证券信息失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
def get_trading_calendar(self, market: str) -> List[int]:
|
|
|
"""
|
|
|
获取交易日历
|
|
|
|
|
|
Args:
|
|
|
market: 市场代码 (SH, SZ, CFE)
|
|
|
|
|
|
Returns:
|
|
|
交易日列表 (YYYYMMDD格式)
|
|
|
"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
if isinstance(self._calendar, list):
|
|
|
return self._calendar
|
|
|
elif hasattr(self._calendar, 'get_trading_calendar'):
|
|
|
return self._calendar.get_trading_calendar(market)
|
|
|
else:
|
|
|
return []
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取交易日历失败: {str(e)}")
|
|
|
return []
|
|
|
|
|
|
def get_adj_factor(self, codes: List[str]) -> pd.DataFrame:
|
|
|
"""获取复权因子"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
return self._base_data.get_adj_factor(codes)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取复权因子失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
def get_backward_factor(self, codes: List[str]) -> pd.DataFrame:
|
|
|
"""获取后复权因子"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
return self._base_data.get_backward_factor(codes)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取后复权因子失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
# ==================== 历史行情接口 ====================
|
|
|
|
|
|
def get_kline(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date,
|
|
|
period: str = "daily"
|
|
|
) -> Dict[str, pd.DataFrame]:
|
|
|
"""
|
|
|
获取K线数据
|
|
|
|
|
|
Args:
|
|
|
codes: 代码列表
|
|
|
start_date: 开始日期 (YYYYMMDD 或 date对象)
|
|
|
end_date: 结束日期 (YYYYMMDD 或 date对象)
|
|
|
period: 周期 (daily, min1, min5, min15, min30, min60)
|
|
|
|
|
|
Returns:
|
|
|
字典 {code: DataFrame}
|
|
|
"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
# 转换日期格式
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
period_map = {
|
|
|
"daily": 10008,
|
|
|
"min1": 10000,
|
|
|
"min5": 10002,
|
|
|
"min15": 10004,
|
|
|
"min30": 10005,
|
|
|
"min60": 10006,
|
|
|
}
|
|
|
|
|
|
period_value = period_map.get(period, 10008)
|
|
|
|
|
|
try:
|
|
|
return self._market_data.query_kline(codes, start_date, end_date, period_value)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取K线数据失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
def get_snapshot(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> Dict[str, pd.DataFrame]:
|
|
|
"""
|
|
|
获取历史快照数据
|
|
|
|
|
|
Args:
|
|
|
codes: 代码列表
|
|
|
start_date: 开始日期
|
|
|
end_date: 结束日期
|
|
|
|
|
|
Returns:
|
|
|
字典 {code: DataFrame}
|
|
|
"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._market_data.query_snapshot(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取快照数据失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
# ==================== 财务数据接口 ====================
|
|
|
|
|
|
def get_balance_sheet(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> Dict[str, pd.DataFrame]:
|
|
|
"""获取资产负债表"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_balance_sheet(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取资产负债表失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
def get_cash_flow(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> Dict[str, pd.DataFrame]:
|
|
|
"""获取现金流量表"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_cash_flow(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取现金流量表失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
def get_income_statement(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> Dict[str, pd.DataFrame]:
|
|
|
"""获取利润表"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_income_statement(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取利润表失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
def get_profit_express(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> pd.DataFrame:
|
|
|
"""获取业绩快报"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_profit_express(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取业绩快报失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
def get_profit_notice(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> pd.DataFrame:
|
|
|
"""获取业绩预告"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_profit_notice(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取业绩预告失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
# ==================== 股东股本接口 ====================
|
|
|
|
|
|
def get_top10_shareholders(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> pd.DataFrame:
|
|
|
"""获取十大股东"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_top10_shareholders(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取十大股东失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
def get_shareholder_count(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> pd.DataFrame:
|
|
|
"""获取股东户数"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_shareholder_count(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取股东户数失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
def get_equity_structure(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> pd.DataFrame:
|
|
|
"""获取股本结构"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_equity_structure(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取股本结构失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
# ==================== 融资融券接口 ====================
|
|
|
|
|
|
def get_margin_summary(
|
|
|
self,
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> pd.DataFrame:
|
|
|
"""获取融资融券汇总"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_margin_summary(start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取融资融券汇总失败: {str(e)}")
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
def get_margin_detail(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> Dict[str, pd.DataFrame]:
|
|
|
"""获取融资融券明细"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_margin_detail(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取融资融券明细失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
# ==================== 指数数据接口 ====================
|
|
|
|
|
|
def get_index_constituents(self, codes: List[str]) -> Dict[str, pd.DataFrame]:
|
|
|
"""获取指数成分股"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_index_constituents(codes)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取指数成分股失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
def get_index_weights(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> Dict[str, pd.DataFrame]:
|
|
|
"""获取指数权重"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_index_weights(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取指数权重失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
# ==================== ETF数据接口 ====================
|
|
|
|
|
|
def get_etf_pcf(self, codes: List[str]) -> Tuple[pd.DataFrame, Dict[str, pd.DataFrame]]:
|
|
|
"""获取ETF申赎数据"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_etf_pcf(codes)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取ETF申赎数据失败: {str(e)}")
|
|
|
return pd.DataFrame(), {}
|
|
|
|
|
|
def get_fund_share(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date,
|
|
|
end_date
|
|
|
) -> Dict[str, pd.DataFrame]:
|
|
|
"""获取基金份额数据"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
if isinstance(start_date, date):
|
|
|
start_date = int(start_date.strftime("%Y%m%d"))
|
|
|
if isinstance(end_date, date):
|
|
|
end_date = int(end_date.strftime("%Y%m%d"))
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_fund_share(codes, start_date, end_date)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取基金份额数据失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
# ==================== 可转债数据接口 ====================
|
|
|
|
|
|
def get_kzz_issuance(self, codes: List[str]) -> Dict[str, pd.DataFrame]:
|
|
|
"""获取可转债发行数据"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
return self._info_data.get_kzz_issuance(codes)
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取可转债发行数据失败: {str(e)}")
|
|
|
return {code: pd.DataFrame() for code in codes}
|
|
|
|
|
|
# ==================== 实时数据订阅 ====================
|
|
|
|
|
|
def subscribe_snapshot(self, codes: List[str], callback: Callable):
|
|
|
"""
|
|
|
订阅实时快照
|
|
|
|
|
|
Args:
|
|
|
codes: 代码列表
|
|
|
callback: 回调函数,接收(code, data)参数
|
|
|
"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
# 使用实时数据接口订阅
|
|
|
realtime = self._ad.RealtimeData()
|
|
|
realtime.subscribe_snapshot(codes, callback)
|
|
|
except Exception as e:
|
|
|
logger.error(f"订阅实时快照失败: {str(e)}")
|
|
|
|
|
|
def subscribe_kline(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
period: str,
|
|
|
callback: Callable
|
|
|
):
|
|
|
"""
|
|
|
订阅实时K线
|
|
|
|
|
|
Args:
|
|
|
codes: 代码列表
|
|
|
period: 周期
|
|
|
callback: 回调函数
|
|
|
"""
|
|
|
if not self._is_connected:
|
|
|
raise RuntimeError("SDK未连接")
|
|
|
|
|
|
try:
|
|
|
realtime = self._ad.RealtimeData()
|
|
|
realtime.subscribe_kline(codes, period, callback)
|
|
|
except Exception as e:
|
|
|
logger.error(f"订阅实时K线失败: {str(e)}")
|
|
|
|
|
|
def unsubscribe(self, codes: List[str] = None):
|
|
|
"""
|
|
|
取消订阅
|
|
|
|
|
|
Args:
|
|
|
codes: 代码列表,None表示取消所有
|
|
|
"""
|
|
|
if not self._is_connected:
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
realtime = self._ad.RealtimeData()
|
|
|
realtime.unsubscribe(codes)
|
|
|
except Exception as e:
|
|
|
logger.error(f"取消订阅失败: {str(e)}")
|