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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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