You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

397 lines
16 KiB

# 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"
]