""" 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: if security_type == "EXTRA_FUTURE": return self._base_data.get_future_code_list() elif security_type == "EXTRA_OPTION": return self._base_data.get_option_code_list() else: return self._base_data.get_code_list(security_type) except Exception as e: logger.error(f"获取代码列表失败: {str(e)}") return [] def get_future_varieties(self) -> List[str]: """ 获取期货品种列表 Returns: 品种代码列表 (如: IF, IC, al, zn, etc.) """ if not self._is_connected: raise RuntimeError("SDK未连接") try: codes = self._base_data.get_future_code_list() varieties = set() for code in codes: # 代码格式: 品种代码+月份+交易所 # 如: IF2401.CFE, al2702.SHF parts = code.split('.') if len(parts) >= 1: contract = parts[0] # 提取品种代码(去掉月份部分) # 月份格式: YYMM (如 2401, 2702) variety = contract[:-4] if len(contract) > 4 else contract varieties.add(variety) return sorted(list(varieties)) except Exception as e: logger.error(f"获取期货品种列表失败: {str(e)}") return [] def get_main_contract(self, variety: str) -> Optional[str]: """ 获取指定品种的当前主力合约 Args: variety: 品种代码 (如: IF, IC, al, zn) Returns: 主力合约代码 (如: IF2401.CFE) """ if not self._is_connected: raise RuntimeError("SDK未连接") try: from datetime import datetime codes = self._base_data.get_future_code_list() current_year = datetime.now().year current_month = datetime.now().month # 筛选该品种的合约 variety_codes = [] for code in codes: parts = code.split('.') if len(parts) >= 1: contract = parts[0] if contract.startswith(variety) and len(contract) > len(variety): variety_codes.append(code) if not variety_codes: return None # 找到最近月份的合约作为主力合约 # 主力合约通常是当前月份或下个月份的合约 def get_contract_month(code): parts = code.split('.') contract = parts[0] month_str = contract[-4:] # YYMM try: year = int(month_str[:2]) + 2000 month = int(month_str[2:]) return (year, month) except: return (9999, 99) # 按月份排序 variety_codes.sort(key=get_contract_month) # 找到第一个大于当前月份的合约 for code in variety_codes: year, month = get_contract_month(code) if year >= current_year and month >= current_month: return code # 如果没有找到,返回最后一个合约 return variety_codes[-1] if variety_codes else None except Exception as e: logger.error(f"获取主力合约失败: {str(e)}") return None def get_all_main_contracts(self) -> Dict[str, str]: """ 获取所有品种的主力合约 Returns: 字典 {品种: 主力合约} """ varieties = self.get_future_varieties() main_contracts = {} for variety in varieties: main_contract = self.get_main_contract(variety) if main_contract: main_contracts[variety] = main_contract return main_contracts 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)}")