# RQData数据适配器 import os import time import pandas as pd from typing import Dict, Optional, List from qihuo_analyzer.data.api_adapters.base_adapter import BaseDataAdapter # 尝试导入rqdatac try: import rqdatac as rqd RQDATA_AVAILABLE = True except Exception as e: print(f"RQData导入失败:{e},将使用模拟数据") RQDATA_AVAILABLE = False class RqDataAdapter(BaseDataAdapter): """RQData数据适配器 使用RQData获取期货数据。 """ def __init__(self): self.api_connected = False def connect(self) -> bool: """连接API Returns: bool: 连接是否成功 """ try: if RQDATA_AVAILABLE: # 使用RQData连接 username = os.getenv('RQDATA_USERNAME', '') password = os.getenv('RQDATA_PASSWORD', '') if username and password: rqd.init(username, password) print("RQData API连接成功") self.api_connected = True return True else: print("RQData账号密码未配置,将使用模拟数据") self.api_connected = False return False else: # 模拟API,用于测试 print("RQData不可用,使用模拟API") self.api_connected = False return False except Exception as e: print(f"RQData API连接失败:{e}") # 模拟API,用于测试 self.api_connected = False return False def disconnect(self): """断开连接""" if self.api_connected: try: # RQData不需要显式断开连接 print("RQData API连接已断开") self.api_connected = False except: pass def _convert_duration(self, duration: str) -> str: """将时间周期字符串转换为RQData格式 Args: duration: 时间周期,如 '1m', '5m', '15m', '1h', '1d' Returns: RQData格式的时间周期 """ duration_map = { '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '60m', '2h': '120m', '4h': '240m', '6h': '360m', '12h': '720m', '1d': '1d', '1w': '1w' } return duration_map.get(duration, '60m') # 默认60分钟 def _convert_symbol(self, symbol: str) -> str: """将合约代码转换为RQData格式 Args: symbol: 合约代码,如 'CU2603' Returns: RQData格式的合约代码,如 'SHFE.CU2603' """ # 交易所映射 exchange_map = { 'CU': 'SHFE', # 铜 - 上海期货交易所 'AL': 'SHFE', # 铝 - 上海期货交易所 'ZN': 'SHFE', # 锌 - 上海期货交易所 'PB': 'SHFE', # 铅 - 上海期货交易所 'NI': 'SHFE', # 镍 - 上海期货交易所 'SN': 'SHFE', # 锡 - 上海期货交易所 'AU': 'SHFE', # 黄金 - 上海期货交易所 'AG': 'SHFE', # 白银 - 上海期货交易所 'RB': 'SHFE', # 螺纹钢 - 上海期货交易所 'HC': 'SHFE', # 热轧卷板 - 上海期货交易所 'BU': 'SHFE', # 沥青 - 上海期货交易所 'RU': 'SHFE', # 橡胶 - 上海期货交易所 'FU': 'SHFE', # 燃油 - 上海期货交易所 'SC': 'INE', # 原油 - 上海国际能源交易中心 'I': 'DCE', # 铁矿石 - 大连商品交易所 'J': 'DCE', # 焦炭 - 大连商品交易所 'JM': 'DCE', # 焦煤 - 大连商品交易所 'A': 'DCE', # 大豆 - 大连商品交易所 'B': 'DCE', # 豆粕 - 大连商品交易所 'M': 'DCE', # 豆粕 - 大连商品交易所 'Y': 'DCE', # 豆油 - 大连商品交易所 'P': 'DCE', # 棕榈油 - 大连商品交易所 'C': 'DCE', # 玉米 - 大连商品交易所 'CS': 'DCE', # 玉米淀粉 - 大连商品交易所 'L': 'DCE', # 聚乙烯 - 大连商品交易所 'V': 'DCE', # 聚氯乙烯 - 大连商品交易所 'PP': 'DCE', # 聚丙烯 - 大连商品交易所 'TA': 'CZCE', # PTA - 郑州商品交易所 'CF': 'CZCE', # 棉花 - 郑州商品交易所 'SR': 'CZCE', # 白糖 - 郑州商品交易所 'MA': 'CZCE', # 甲醇 - 郑州商品交易所 'ZC': 'CZCE', # 动力煤 - 郑州商品交易所 'FG': 'CZCE', # 玻璃 - 郑州商品交易所 'RM': 'CZCE', # 菜籽粕 - 郑州商品交易所 'OI': 'CZCE', # 菜籽油 - 郑州商品交易所 'RS': 'CZCE', # 菜籽 - 郑州商品交易所 'WH': 'CZCE', # 强麦 - 郑州商品交易所 'JR': 'CZCE', # 粳稻 - 郑州商品交易所 'LR': 'CZCE', # 晚籼稻 - 郑州商品交易所 } # 提取品种代码和合约月份 if len(symbol) >= 4: product_code = symbol[:2].upper() contract_month = symbol[2:].upper() # 获取交易所代码 exchange = exchange_map.get(product_code, 'SHFE') # 构建RQData格式的合约代码 rq_symbol = f"{exchange}.{product_code}{contract_month}" return rq_symbol else: return symbol def get_kline_data(self, symbol: str, duration: str, count: int = 200) -> Optional[pd.DataFrame]: """获取K线数据 Args: symbol: 合约代码 duration: 时间周期,如 '1m', '5m', '15m', '1h', '1d' count: 数据数量 Returns: K线数据DataFrame,如果无法获取真实数据则返回None """ try: if RQDATA_AVAILABLE and self.api_connected: # 转换合约代码为RQData格式 rq_symbol = self._convert_symbol(symbol) print(f"使用RQData格式合约代码: {rq_symbol}") # 转换时间周期为RQData格式 rq_duration = self._convert_duration(duration) # 计算开始时间 from datetime import datetime, timedelta end_date = datetime.now() # 根据时间周期计算开始日期 if rq_duration == '1d': start_date = end_date - timedelta(days=count) elif rq_duration == '1w': start_date = end_date - timedelta(weeks=count) else: # 对于分钟级别,计算大致的天数 minutes_per_period = int(rq_duration[:-1]) total_minutes = minutes_per_period * count start_date = end_date - timedelta(minutes=total_minutes) # 使用RQData获取K线数据 df = rqd.get_price( rq_symbol, start_date=start_date, end_date=end_date, frequency=rq_duration, fields=['open', 'high', 'low', 'close', 'volume', 'open_interest'], adjust_type='none' ) if not df.empty: print(f"成功获取K线数据,数据长度: {len(df)}") return df else: print("获取K线数据失败:无数据返回") return None else: # 不再自动返回模拟数据,返回None print(f"无法获取真实数据:{'API未连接' if not self.api_connected else 'RQData不可用'}") return None except Exception as e: print(f"获取K线数据失败:{e}") # 不再自动返回模拟数据,返回None return None def get_tick_data(self, symbol: str, count: int = 1000) -> Optional[pd.DataFrame]: """获取Tick数据""" try: if RQDATA_AVAILABLE and self.api_connected: # 转换合约代码为RQData格式 rq_symbol = self._convert_symbol(symbol) print(f"使用RQData格式合约代码: {rq_symbol}") # 计算开始时间 from datetime import datetime, timedelta end_date = datetime.now() start_date = end_date - timedelta(days=1) # RQData Tick数据通常只能获取最近1天 # 使用RQData获取Tick数据 df = rqd.get_price( rq_symbol, start_date=start_date, end_date=end_date, frequency='tick', fields=['last', 'volume', 'open_interest', 'bid_price1', 'bid_volume1', 'ask_price1', 'ask_volume1'], adjust_type='none' ) if not df.empty: # 重命名列以保持与原来的接口一致 df = df.rename(columns={ 'last': 'last_price', 'bid_price1': 'bid_price1', 'bid_volume1': 'bid_volume1', 'ask_price1': 'ask_price1', 'ask_volume1': 'ask_volume1' }) print(f"成功获取Tick数据,数据长度: {len(df)}") return df.tail(count) # 只返回最近的count条数据 else: print("获取Tick数据失败:无数据返回") return None else: # 返回模拟数据 print(f"无法获取真实数据:{'API未连接' if not self.api_connected else 'RQData不可用'}") return None except Exception as e: print(f"获取Tick数据失败:{e}") return None def get_contract_info(self, symbol: str) -> Optional[Dict]: """获取合约信息""" try: if RQDATA_AVAILABLE and self.api_connected: # 转换合约代码为RQData格式 rq_symbol = self._convert_symbol(symbol) print(f"使用RQData格式合约代码: {rq_symbol}") # 使用RQData获取合约信息 instrument = rqd.instruments(rq_symbol) if instrument: return { 'symbol': symbol, 'name': instrument[0].name, 'exchange': instrument[0].exchange, 'product': instrument[0].underlying_symbol, 'price_tick': instrument[0].price_tick, 'volume_multiple': instrument[0].contract_multiplier, 'margin_rate': instrument[0].margin_rate, 'expire_datetime': instrument[0].maturity_date, 'create_datetime': instrument[0].listed_date } else: print("获取合约信息失败:合约不存在") return None else: # 返回模拟数据 print(f"无法获取真实数据:{'API未连接' if not self.api_connected else 'RQData不可用'}") return None except Exception as e: print(f"获取合约信息失败:{e}") return None def get_market_data(self, symbols: List[str]) -> Dict[str, Dict]: """批量获取市场数据""" market_data = {} for symbol in symbols: try: if RQDATA_AVAILABLE and self.api_connected: # 转换合约代码为RQData格式 rq_symbol = self._convert_symbol(symbol) print(f"使用RQData格式合约代码: {rq_symbol}") # 使用RQData获取最新行情数据 quote = rqd.get_quote(rq_symbol) if not quote.empty: market_data[symbol] = { 'latest_price': quote['last'].iloc[0], 'open': quote['open'].iloc[0], 'high': quote['high'].iloc[0], 'low': quote['low'].iloc[0], 'pre_close': quote['prev_close'].iloc[0], 'volume': quote['volume'].iloc[0], 'open_interest': quote['open_interest'].iloc[0], 'bid_price1': quote['bid1'].iloc[0], 'ask_price1': quote['ask1'].iloc[0] } else: print(f"获取{symbol}市场数据失败:无数据返回") market_data[symbol] = { 'latest_price': 0, 'open': 0, 'high': 0, 'low': 0, 'pre_close': 0, 'volume': 0, 'open_interest': 0, 'bid_price1': 0, 'ask_price1': 0 } else: # 模拟数据 market_data[symbol] = { 'latest_price': 0, 'open': 0, 'high': 0, 'low': 0, 'pre_close': 0, 'volume': 0, 'open_interest': 0, 'bid_price1': 0, 'ask_price1': 0 } except Exception as e: print(f"获取{symbol}市场数据失败:{e}") market_data[symbol] = { 'latest_price': 0, 'open': 0, 'high': 0, 'low': 0, 'pre_close': 0, 'volume': 0, 'open_interest': 0, 'bid_price1': 0, 'ask_price1': 0 } return market_data def get_all_symbols(self) -> List[str]: """获取所有品种列表 Returns: List[str]: 所有品种的合约代码列表 """ try: # 直接使用本地枚举数据,不使用RQData获取 print("使用本地枚举品种列表") # 从get_all_symbols_by_exchange获取所有品种 from qihuo_analyzer.data.data_fetcher import DataFetcher data_fetcher = DataFetcher() symbols_by_exchange = data_fetcher.get_all_symbols_by_exchange() symbols = [] for exchange, products in symbols_by_exchange.items(): for product, product_data in products.items(): # 使用每个品种的第一个合约作为代表 if product_data['contracts']: symbols.append(product_data['contracts'][0]) return symbols except Exception as e: print(f"获取所有品种列表失败:{e}") # 返回模拟数据 return [ "CU2603", "AL2603", "ZN2603", "PB2603", "NI2603", "SN2603", "AU2603", "AG2603", "RB2603", "HC2603", "BU2603", "RU2603", "SC2603", "I2603", "J2603", "JM2603", "A2603", "M2603", "Y2603", "P2603", "C2603", "CS2603", "L2603", "V2603", "PP2603", "TA2603", "CF2603", "SR2603", "MA2603", "FG2603" ]