feat:增加多数据源适配

develop
Lxy 4 months ago
parent bf49c39d98
commit b06e29a95c

@ -21,7 +21,7 @@ pip --version
pip install -r requirements.txt pip install -r requirements.txt
``` ```
### 2.2 解决可能的依赖问题 ### 2.2 解决可能的依赖问题
- tqsdk 导入失败 : 系统会自动使用模拟数据,不影响基本功能 - RQData 导入失败 : 系统会自动使用模拟数据,不影响基本功能
- TA-Lib 安装失败 : 可以注释掉该依赖,系统会使用内置的技术分析函数 - TA-Lib 安装失败 : 可以注释掉该依赖,系统会使用内置的技术分析函数
## 3. 配置设置 ## 3. 配置设置
### 3.1 创建环境配置文件 ### 3.1 创建环境配置文件

@ -15,6 +15,10 @@ TQSDK_PASSWORD=1qazse42W3
TQSERVER_HOST=api.shinnytech.com TQSERVER_HOST=api.shinnytech.com
TQSERVER_PORT=7777 TQSERVER_PORT=7777
#米筐RQData配置
RQDATA_USERNAME=18600025116
RQDATA_PASSWORD=1qazse42W3
# 数据库配置 # 数据库配置
DB_PATH=./data/futures_analysis.db DB_PATH=./data/futures_analysis.db

@ -0,0 +1,18 @@
---
name: AlphaFutures
description: A brief description of what this skill does
---
# AlphaFutures
Instructions for the agent to follow when this skill is activated.
## When to use
Describe when this skill should be used.
## Instructions
1. First step
2. Second step
3. Additional steps as needed

@ -0,0 +1,29 @@
# 数据源配置文件
# 统一数据获取接口的配置参数
# 数据源类型配置
# 可选值: tqsdk, rqdata
DATA_ADAPTER_TYPE = "tqsdk"
# TQSDK账号配置可选
# 未配置时会使用模拟数据
TQSDK_USERNAME = "windsdreamer"
TQSDK_PASSWORD = "1qazse42W3"
# RQData账号配置可选
# 未配置时会使用模拟数据
RQDATA_USERNAME = "18600025116"
RQDATA_PASSWORD = "1qazse42W3"
# 调试模式配置
# 设置为True时启用详细日志
DEBUG = False
# 数据源连接超时设置(秒)
CONNECTION_TIMEOUT = 30
# 数据缓存配置
# 设置为True时启用数据缓存提高重复查询性能
ENABLE_CACHE = True
# 缓存过期时间(秒)
CACHE_EXPIRY = 3600

@ -0,0 +1,12 @@
# API适配器包初始化文件
from qihuo_analyzer.data.api_adapters.base_adapter import BaseDataAdapter
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
from qihuo_analyzer.data.api_adapters.rqdata_adapter import RqDataAdapter
from qihuo_analyzer.data.api_adapters.adapter_factory import DataAdapterFactory
__all__ = [
'BaseDataAdapter',
'TqSdkAdapter',
'RqDataAdapter',
'DataAdapterFactory'
]

@ -0,0 +1,38 @@
# 适配器工厂类
from qihuo_analyzer.data.api_adapters.base_adapter import BaseDataAdapter
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
from qihuo_analyzer.data.api_adapters.rqdata_adapter import RqDataAdapter
import os
class DataAdapterFactory:
"""数据适配器工厂类
根据配置创建相应的数据适配器实例
"""
@staticmethod
def create_adapter(adapter_type: str = None) -> BaseDataAdapter:
"""创建数据适配器
Args:
adapter_type: 适配器类型可选值'tqsdk', 'rqdata'如果为None则从环境变量获取
Returns:
BaseDataAdapter: 数据适配器实例
"""
# 如果没有指定适配器类型,从环境变量获取
if adapter_type is None:
adapter_type = os.getenv('DATA_ADAPTER_TYPE', 'tqsdk').lower()
# 根据类型创建适配器
if adapter_type == 'tqsdk':
print("创建TQSDK数据适配器")
return TqSdkAdapter()
elif adapter_type == 'rqdata':
print("创建RQData数据适配器")
return RqDataAdapter()
else:
# 默认使用TQSDK适配器
print(f"未知的适配器类型:{adapter_type}使用默认的TQSDK适配器")
return TqSdkAdapter()

@ -0,0 +1,85 @@
# 数据获取适配器基类
from abc import ABC, abstractmethod
from typing import Dict, Optional, List
import pandas as pd
class BaseDataAdapter(ABC):
"""数据获取适配器基类
所有数据获取适配器都需要实现这个接口确保统一的方法调用方式
"""
@abstractmethod
def connect(self) -> bool:
"""连接API
Returns:
bool: 连接是否成功
"""
pass
@abstractmethod
def disconnect(self):
"""断开连接"""
pass
@abstractmethod
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
"""
pass
@abstractmethod
def get_tick_data(self, symbol: str, count: int = 1000) -> Optional[pd.DataFrame]:
"""获取Tick数据
Args:
symbol: 合约代码
count: 数据数量
Returns:
Tick数据DataFrame如果无法获取真实数据则返回None
"""
pass
@abstractmethod
def get_contract_info(self, symbol: str) -> Optional[Dict]:
"""获取合约信息
Args:
symbol: 合约代码
Returns:
合约信息字典如果无法获取真实数据则返回None
"""
pass
@abstractmethod
def get_market_data(self, symbols: List[str]) -> Dict[str, Dict]:
"""批量获取市场数据
Args:
symbols: 合约代码列表
Returns:
市场数据字典键为合约代码值为市场数据
"""
pass
@abstractmethod
def get_all_symbols(self) -> List[str]:
"""获取所有品种列表
Returns:
所有品种的合约代码列表
"""
pass

@ -0,0 +1,396 @@
# 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"
]

@ -0,0 +1,335 @@
# TQSDK数据适配器
import os
import time
import pandas as pd
from typing import Dict, Optional, List
from qihuo_analyzer.data.api_adapters.base_adapter import BaseDataAdapter
# 尝试导入tqsdk
try:
from tqsdk import TqApi, TqAuth
TQSDK_AVAILABLE = True
except Exception as e:
print(f"tqsdk导入失败{e},将使用模拟数据")
TQSDK_AVAILABLE = False
class TqSdkAdapter(BaseDataAdapter):
"""TQSDK数据适配器
使用天勤TQSDK获取期货数据
"""
def __init__(self):
self.api = None
# 交易所映射
self.exchange_map = {
'AU': 'SHFE', # 黄金 - 上海期货交易所
'AG': 'SHFE', # 白银 - 上海期货交易所
'CU': 'SHFE', # 铜 - 上海期货交易所
'NI': 'SHFE', # 镍 - 上海期货交易所
'SN': 'SHFE', # 锡 - 上海期货交易所
}
def connect(self) -> bool:
"""连接API
Returns:
bool: 连接是否成功
"""
try:
if TQSDK_AVAILABLE:
# 使用天勤TQSDK连接
username = os.getenv('TQSDK_USERNAME', '')
password = os.getenv('TQSDK_PASSWORD', '')
if username and password:
self.api = TqApi(auth=TqAuth(username, password))
print("TQSDK API连接成功")
return True
else:
print("TQSDK账号密码未配置将使用模拟数据")
self.api = None
return False
else:
# 模拟API用于测试
print("TQSDK不可用使用模拟API")
self.api = None
return False
except Exception as e:
print(f"TQSDK API连接失败{e}")
# 模拟API用于测试
self.api = None
return False
def disconnect(self):
"""断开连接"""
if self.api:
try:
self.api.close()
print("TQSDK API连接已断开")
except:
pass
def _convert_duration(self, duration: str) -> int:
"""将时间周期字符串转换为分钟数
Args:
duration: 时间周期 '1m', '5m', '15m', '1h', '1d'
Returns:
分钟数
"""
duration_map = {
'1m': 1,
'5m': 5,
'15m': 15,
'30m': 30,
'1h': 60,
'2h': 120,
'4h': 240,
'6h': 360,
'12h': 720,
'1d': 1440,
'1w': 10080
}
return duration_map.get(duration, 60) # 默认60分钟
def _convert_symbol(self, symbol: str) -> str:
"""将合约代码转换为TQSDK格式
Args:
symbol: 合约代码 'CU2603'
Returns:
TQSDK格式的合约代码 'SHFE.cu2603'
"""
# 提取品种代码和合约月份
if len(symbol) >= 4:
# 3字符品种代码
if len(symbol) >= 5:
product_code = symbol[:3].upper()
if product_code in self.exchange_map:
contract_month = symbol[3:].lower()
exchange = self.exchange_map[product_code]
return f"{exchange}.{product_code.lower()}{contract_month}"
# 2字符品种代码
product_code = symbol[:2].upper()
if product_code in self.exchange_map:
contract_month = symbol[2:].lower()
exchange = self.exchange_map[product_code]
return f"{exchange}.{product_code.lower()}{contract_month}"
# 1字符品种代码
product_code = symbol[:1].upper()
if product_code in self.exchange_map:
contract_month = symbol[1:].lower()
exchange = self.exchange_map[product_code]
return f"{exchange}.{product_code.lower()}{contract_month}"
# 无法识别的合约代码,返回原始代码
return 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 TQSDK_AVAILABLE and self.api:
# 转换合约代码为TQSDK格式
tq_symbol = self._convert_symbol(symbol)
print(f"使用TQSDK格式合约代码: {tq_symbol}")
# 转换时间周期为分钟数
duration_minutes = self._convert_duration(duration)
# 使用真实API获取数据
klines = self.api.get_kline_serial(tq_symbol, duration_minutes, data_length=count)
# 等待数据准备就绪
import time
start_time = time.time()
timeout = 5 # 5秒超时
while True:
if hasattr(klines, 'datetime') and len(klines.datetime) > 0:
break
if time.time() - start_time > timeout:
print("获取K线数据超时")
return None
time.sleep(0.1)
# 转换为DataFrame
data = {
'datetime': klines.datetime,
'open': klines.open,
'high': klines.high,
'low': klines.low,
'close': klines.close,
'volume': klines.volume,
'open_interest': klines.open_oi
}
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['datetime'], unit='ns')
df.set_index('datetime', inplace=True)
print(f"成功获取K线数据数据长度: {len(df)}")
return df
else:
# 不再自动返回模拟数据返回None
print(f"无法获取真实数据:{'API未连接' if not self.api else 'TQSDK不可用'}")
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 TQSDK_AVAILABLE and self.api:
# 使用真实API获取数据
ticks = self.api.get_tick_serial(symbol, data_length=count)
self.api.wait_update()
# 转换为DataFrame
data = {
'datetime': ticks.datetime,
'last_price': ticks.last_price,
'volume': ticks.volume,
'open_interest': ticks.open_interest,
'bid_price1': ticks.bid_price1,
'bid_volume1': ticks.bid_volume1,
'ask_price1': ticks.ask_price1,
'ask_volume1': ticks.ask_volume1
}
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['datetime'], unit='ns')
df.set_index('datetime', inplace=True)
return df
else:
# 返回模拟数据
print(f"无法获取真实数据:{'API未连接' if not self.api else 'TQSDK不可用'}")
return None
except Exception as e:
print(f"获取Tick数据失败{e}")
return None
def get_contract_info(self, symbol: str) -> Optional[Dict]:
"""获取合约信息"""
try:
if TQSDK_AVAILABLE and self.api:
# 使用真实API获取数据
quote = self.api.get_quote(symbol)
self.api.wait_update()
return {
'symbol': symbol,
'name': quote.instrument_name,
'exchange': quote.exchange_id,
'product': quote.product_id,
'price_tick': quote.price_tick,
'volume_multiple': quote.volume_multiple,
'margin_rate': quote.margin_rate,
'expire_datetime': quote.expire_datetime,
'create_datetime': quote.create_datetime
}
else:
# 返回模拟数据
print(f"无法获取真实数据:{'API未连接' if not self.api else 'TQSDK不可用'}")
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 TQSDK_AVAILABLE and self.api:
quote = self.api.get_quote(symbol)
self.api.wait_update()
market_data[symbol] = {
'latest_price': quote.last_price,
'open': quote.open,
'high': quote.high,
'low': quote.low,
'pre_close': quote.pre_close,
'volume': quote.volume,
'open_interest': quote.open_interest,
'bid_price1': quote.bid_price1,
'ask_price1': quote.ask_price1
}
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:
if TQSDK_AVAILABLE and self.api:
# TQSDK 没有直接获取所有品种列表的方法,使用模拟数据
print("TQSDK 不支持获取所有品种列表,使用模拟数据")
return self._get_mock_all_symbols()
else:
# 返回模拟数据
print("使用模拟品种列表")
return self._get_mock_all_symbols()
except Exception as e:
print(f"获取所有品种列表失败:{e}")
return self._get_mock_all_symbols()
def _get_mock_all_symbols(self) -> List[str]:
"""获取模拟品种列表"""
# 返回exchange_map中映射的所有品种
symbols = []
# 为每个品种生成一个合约代码使用2603月份
for product_code in self.exchange_map:
# 生成合约代码,格式:品种代码+2603
contract_code = f"{product_code}2603"
symbols.append(contract_code)
print(f"模拟品种列表: {symbols}")
return symbols

@ -4,148 +4,40 @@ import time
import pandas as pd import pandas as pd
from typing import Dict, Optional, List from typing import Dict, Optional, List
from qihuo_analyzer.utils.config_manager import config_manager from qihuo_analyzer.utils.config_manager import config_manager
from qihuo_analyzer.data.api_adapters import DataAdapterFactory
# 尝试导入tqsdk如果失败则使用模拟数据
try:
from tqsdk import TqApi, TqAuth
TQSDK_AVAILABLE = True
except Exception as e:
print(f"tqsdk导入失败{e},将使用模拟数据")
TQSDK_AVAILABLE = False
class DataFetcher: class DataFetcher:
"""数据获取器""" """数据获取器"""
def __init__(self): def __init__(self):
self.api = None # 使用适配器工厂创建数据适配器
self.adapter = DataAdapterFactory.create_adapter()
self.api_connected = False
def connect(self) -> bool: def connect(self) -> bool:
"""连接API""" """连接API"""
try: try:
if TQSDK_AVAILABLE: # 使用适配器的connect方法
# 使用天勤TQSDK连接 success = self.adapter.connect()
from qihuo_analyzer.utils.config_manager import config_manager self.api_connected = success
username = os.getenv('TQSDK_USERNAME', '') return success
password = os.getenv('TQSDK_PASSWORD', '')
if username and password:
self.api = TqApi(auth=TqAuth(username, password))
print("API连接成功")
return True
else:
print("TQSDK账号密码未配置将使用模拟数据")
self.api = None
return False
else:
# 模拟API用于测试
print("使用模拟API")
self.api = None
return False
except Exception as e: except Exception as e:
print(f"API连接失败{e}") print(f"API连接失败{e}")
# 模拟API用于测试 self.api_connected = False
self.api = None
return False return False
def disconnect(self): def disconnect(self):
"""断开连接""" """断开连接"""
if self.api: if self.api_connected:
try: try:
self.api.close() # 使用适配器的disconnect方法
print("API连接已断开") self.adapter.disconnect()
self.api_connected = False
except: except:
pass pass
def _convert_duration(self, duration: str) -> int:
"""将时间周期字符串转换为分钟数
Args:
duration: 时间周期 '1m', '5m', '15m', '1h', '1d'
Returns:
分钟数
"""
duration_map = {
'1m': 1,
'5m': 5,
'15m': 15,
'30m': 30,
'1h': 60,
'2h': 120,
'4h': 240,
'6h': 360,
'12h': 720,
'1d': 1440,
'1w': 10080
}
return duration_map.get(duration, 60) # 默认60分钟
def _convert_symbol(self, symbol: str) -> str:
"""将合约代码转换为TQSDK格式
Args:
symbol: 合约代码 'CU2603'
Returns:
TQSDK格式的合约代码 '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:].lower()
# 获取交易所代码
exchange = exchange_map.get(product_code, 'SHFE')
# 构建TQSDK格式的合约代码
tq_symbol = f"{exchange}.{product_code.lower()}{contract_month}"
return tq_symbol
else:
return symbol
def get_product_name_cn(self, symbol: str) -> str: def get_product_name_cn(self, symbol: str) -> str:
"""获取合约的中文名称 """获取合约的中文名称
@ -214,84 +106,29 @@ class DataFetcher:
count: 数据数量 count: 数据数量
Returns: Returns:
K线数据DataFrame如果无法获取真实数据则返回None K线数据DataFrame如果无法获取真实数据则返回模拟数据
""" """
try: try:
if TQSDK_AVAILABLE and self.api: # 使用适配器的get_kline_data方法
# 转换合约代码为TQSDK格式 result = self.adapter.get_kline_data(symbol, duration, count)
tq_symbol = self._convert_symbol(symbol) if result is None:
print(f"使用TQSDK格式合约代码: {tq_symbol}") # 如果适配器返回None使用模拟数据
print("适配器返回None使用模拟K线数据")
# 转换时间周期为分钟数 return self._get_mock_kline_data(symbol, duration, count)
duration_minutes = self._convert_duration(duration) return result
# 使用真实API获取数据
klines = self.api.get_kline_serial(tq_symbol, duration_minutes, data_length=count)
# 等待数据准备就绪
import time
start_time = time.time()
timeout = 5 # 5秒超时
while True:
if hasattr(klines, 'datetime') and len(klines.datetime) > 0:
break
if time.time() - start_time > timeout:
print("获取K线数据超时")
return None
time.sleep(0.1)
# 转换为DataFrame
data = {
'datetime': klines.datetime,
'open': klines.open,
'high': klines.high,
'low': klines.low,
'close': klines.close,
'volume': klines.volume,
'open_interest': klines.open_oi
}
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['datetime'], unit='ns')
df.set_index('datetime', inplace=True)
print(f"成功获取K线数据数据长度: {len(df)}")
return df
else:
# 不再自动返回模拟数据返回None
print(f"无法获取真实数据:{'API未连接' if not self.api else 'TQSDK不可用'}")
return None
except Exception as e: except Exception as e:
print(f"获取K线数据失败{e}") print(f"获取K线数据失败{e}")
# 不再自动返回模拟数据返回None return self._get_mock_kline_data(symbol, duration, count)
return None
def get_tick_data(self, symbol: str, count: int = 1000) -> Optional[pd.DataFrame]: def get_tick_data(self, symbol: str, count: int = 1000) -> Optional[pd.DataFrame]:
"""获取Tick数据""" """获取Tick数据"""
try: try:
if TQSDK_AVAILABLE and self.api: # 使用适配器的get_tick_data方法
# 使用真实API获取数据 result = self.adapter.get_tick_data(symbol, count)
ticks = self.api.get_tick_serial(symbol, data_length=count) if result is None:
self.api.wait_update() # 如果适配器返回None使用模拟数据
# 转换为DataFrame
data = {
'datetime': ticks.datetime,
'last_price': ticks.last_price,
'volume': ticks.volume,
'open_interest': ticks.open_interest,
'bid_price1': ticks.bid_price1,
'bid_volume1': ticks.bid_volume1,
'ask_price1': ticks.ask_price1,
'ask_volume1': ticks.ask_volume1
}
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['datetime'], unit='ns')
df.set_index('datetime', inplace=True)
return df
else:
# 返回模拟数据
return self._get_mock_tick_data(symbol, count) return self._get_mock_tick_data(symbol, count)
return result
except Exception as e: except Exception as e:
print(f"获取Tick数据失败{e}") print(f"获取Tick数据失败{e}")
return self._get_mock_tick_data(symbol, count) return self._get_mock_tick_data(symbol, count)
@ -299,57 +136,35 @@ class DataFetcher:
def get_contract_info(self, symbol: str) -> Optional[Dict]: def get_contract_info(self, symbol: str) -> Optional[Dict]:
"""获取合约信息""" """获取合约信息"""
try: try:
if TQSDK_AVAILABLE and self.api: # 使用适配器的get_contract_info方法
# 使用真实API获取数据 result = self.adapter.get_contract_info(symbol)
quote = self.api.get_quote(symbol) if result is None:
self.api.wait_update() # 如果适配器返回None使用模拟数据
return {
'symbol': symbol,
'name': quote.instrument_name,
'exchange': quote.exchange_id,
'product': quote.product_id,
'price_tick': quote.price_tick,
'volume_multiple': quote.volume_multiple,
'margin_rate': quote.margin_rate,
'expire_datetime': quote.expire_datetime,
'create_datetime': quote.create_datetime
}
else:
# 返回模拟数据
return self._get_mock_contract_info(symbol) return self._get_mock_contract_info(symbol)
return result
except Exception as e: except Exception as e:
print(f"获取合约信息失败:{e}") print(f"获取合约信息失败:{e}")
return self._get_mock_contract_info(symbol) return self._get_mock_contract_info(symbol)
def get_market_data(self, symbols: List[str]) -> Dict[str, Dict]: def get_market_data(self, symbols: List[str]) -> Dict[str, Dict]:
"""批量获取市场数据""" """批量获取市场数据"""
market_data = {}
for symbol in symbols:
try: try:
if TQSDK_AVAILABLE and self.api: # 使用适配器的get_market_data方法
quote = self.api.get_quote(symbol) result = self.adapter.get_market_data(symbols)
self.api.wait_update() if result:
return result
market_data[symbol] = {
'latest_price': quote.last_price,
'open': quote.open,
'high': quote.high,
'low': quote.low,
'pre_close': quote.pre_close,
'volume': quote.volume,
'open_interest': quote.open_interest,
'bid_price1': quote.bid_price1,
'ask_price1': quote.ask_price1
}
else: else:
# 模拟数据 # 如果适配器返回空,使用模拟数据
market_data = {}
for symbol in symbols:
market_data[symbol] = self._get_mock_market_data(symbol) market_data[symbol] = self._get_mock_market_data(symbol)
return market_data
except Exception as e: except Exception as e:
print(f"获取{symbol}市场数据失败:{e}") print(f"获取市场数据失败:{e}")
# 使用模拟数据
market_data = {}
for symbol in symbols:
market_data[symbol] = self._get_mock_market_data(symbol) market_data[symbol] = self._get_mock_market_data(symbol)
return market_data return market_data
def _get_mock_kline_data(self, symbol: str, duration: str, count: int) -> pd.DataFrame: def _get_mock_kline_data(self, symbol: str, duration: str, count: int) -> pd.DataFrame:
@ -362,6 +177,8 @@ class DataFetcher:
freq = '5T' freq = '5T'
elif duration == '15m': elif duration == '15m':
freq = '15T' freq = '15T'
elif duration == '30m':
freq = '30T'
elif duration == '1h': elif duration == '1h':
freq = '1H' freq = '1H'
elif duration == '1d': elif duration == '1d':
@ -463,29 +280,109 @@ class DataFetcher:
List[str]: 所有品种的合约代码列表 List[str]: 所有品种的合约代码列表
""" """
try: try:
if TQSDK_AVAILABLE and self.api: # 使用适配器的get_all_symbols方法
# TQSDK 没有 get_instrument_info 方法,我们使用模拟数据 result = self.adapter.get_all_symbols()
print("TQSDK 不支持获取所有品种列表,使用模拟数据") if result:
return self._get_mock_all_symbols() return result
else: else:
# 返回模拟数据 # 如果适配器返回空,使用本地枚举数据
print("使用模拟品种列表") print("使用本地枚举品种列表")
return self._get_mock_all_symbols() symbols_by_exchange = self.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: except Exception as e:
print(f"获取所有品种列表失败:{e}") print(f"获取所有品种列表失败:{e}")
return self._get_mock_all_symbols() return self._get_mock_all_symbols()
def _get_mock_all_symbols(self) -> List[str]: def _get_mock_all_symbols(self) -> List[str]:
"""获取模拟品种列表""" """获取模拟品种列表"""
# 返回常用的期货品种 # 返回户指定所有期货品种
return [ return [
"CU2603", "AL2603", "ZN2603", "PB2603", "NI2603", "SN2603", "AU2603", "AG2603", "CU2603", "NI2603", "SN2603", "FG2603",
"AU2603", "AG2603", "RB2603", "HC2603", "BU2603", "RU2603", "LY2603", "SA2603", "JM2603", "RB2603", "ALO2603", "MA2603",
"SC2603", "I2603", "J2603", "JM2603", "A2603", "M2603", "V2603", "FU2603", "SC2603", "AL2603", "P2603", "LI2603",
"Y2603", "P2603", "C2603", "CS2603", "L2603", "V2603", "SI2603", "RU2603", "BR2603", "ZN2603", "NR2603", "SP2603",
"PP2603", "TA2603", "CF2603", "SR2603", "MA2603", "FG2603" "IM2603", "IC2603", "LU2603", "IH2603"
] ]
def get_all_symbols_by_exchange(self) -> Dict[str, Dict[str, List[str]]]:
"""获取所有品种列表,按交易所-合约划分
Returns:
Dict[str, Dict[str, List[str]]]: 按交易所-合约划分的品种列表
"""
# 本地枚举数据,按交易所-合约划分
symbols_by_exchange = {
"SHFE": { # 上海期货交易所
"AU": ["AU2603", "AU2604", "AU2605", "AU2606", "AU2607", "AU2608", "AU2609"], # 黄金
"AG": ["AG2603", "AG2604", "AG2605", "AG2606", "AG2607", "AG2608", "AG2609"], # 白银
"CU": ["CU2603", "CU2604", "CU2605", "CU2606", "CU2607", "CU2608", "CU2609"], # 铜
"NI": ["NI2603", "NI2604", "NI2605", "NI2606", "NI2607", "NI2608", "NI2609"], # 镍
"SN": ["SN2603", "SN2604", "SN2605", "SN2606", "SN2607", "SN2608", "SN2609"], # 锡
"FG": ["FG2603", "FG2604", "FG2605", "FG2606", "FG2607", "FG2608", "FG2609"], # 玻璃
"RB": ["RB2603", "RB2604", "RB2605", "RB2606", "RB2607", "RB2608", "RB2609"], # 螺纹钢
"AL": ["AL2603", "AL2604", "AL2605", "AL2606", "AL2607", "AL2608", "AL2609"], # 铝
"ZN": ["ZN2603", "ZN2604", "ZN2605", "ZN2606", "ZN2607", "ZN2608", "ZN2609"], # 锌
"RU": ["RU2603", "RU2604", "RU2605", "RU2606", "RU2607", "RU2608", "RU2609"], # 橡胶
"NR": ["NR2603", "NR2604", "NR2605", "NR2606", "NR2607", "NR2608", "NR2609"], # 20号胶
"FU": ["FU2603", "FU2604", "FU2605", "FU2606", "FU2607", "FU2608", "FU2609"], # 燃油
"SC": ["SC2603", "SC2604", "SC2605", "SC2606", "SC2607", "SC2608", "SC2609"], # 原油
"LU": ["LU2603", "LU2604", "LU2605", "LU2606", "LU2607", "LU2608", "LU2609"], # 低硫燃油
"ALO": ["ALO2603", "ALO2604", "ALO2605", "ALO2606", "ALO2607", "ALO2608", "ALO2609"], # 氧化铝
"LI": ["LI2603", "LI2604", "LI2605", "LI2606", "LI2607", "LI2608", "LI2609"], # 碳酸锂
"SI": ["SI2603", "SI2604", "SI2605", "SI2606", "SI2607", "SI2608", "SI2609"] # 工业硅
},
"INE": { # 上海国际能源交易中心
"SC": ["SC2603", "SC2604", "SC2605", "SC2606", "SC2607", "SC2608", "SC2609"], # 原油
"LU": ["LU2603", "LU2604", "LU2605", "LU2606", "LU2607", "LU2608", "LU2609"] # 低硫燃油
},
"DCE": { # 大连商品交易所
"JM": ["JM2603", "JM2604", "JM2605", "JM2606", "JM2607", "JM2608", "JM2609"], # 焦煤
"P": ["P2603", "P2604", "P2605", "P2606", "P2607", "P2608", "P2609"], # 棕榈油
"V": ["V2603", "V2604", "V2605", "V2606", "V2607", "V2608", "V2609"], # PVC
"MA": ["MA2603", "MA2604", "MA2605", "MA2606", "MA2607", "MA2608", "MA2609"], # 甲醇
"BR": ["BR2603", "BR2604", "BR2605", "BR2606", "BR2607", "BR2608", "BR2609"] # 合成橡胶
},
"CZCE": { # 郑州商品交易所
"FG": ["FG2603", "FG2604", "FG2605", "FG2606", "FG2607", "FG2608", "FG2609"], # 玻璃
"MA": ["MA2603", "MA2604", "MA2605", "MA2606", "MA2607", "MA2608", "MA2609"], # 甲醇
"V": ["V2603", "V2604", "V2605", "V2606", "V2607", "V2608", "V2609"], # PVC
"SA": ["SA2603", "SA2604", "SA2605", "SA2606", "SA2607", "SA2608", "SA2609"], # 纯碱
"LY": ["LY2603", "LY2604", "LY2605", "LY2606", "LY2607", "LY2608", "LY2609"] # 烧碱
},
"CFFEX": { # 中国金融期货交易所
"IH": ["IH2603", "IH2604", "IH2605", "IH2606", "IH2607", "IH2608", "IH2609"], # 上证50
"IC": ["IC2603", "IC2604", "IC2605", "IC2606", "IC2607", "IC2608", "IC2609"], # 中证500
"IM": ["IM2603", "IM2604", "IM2605", "IM2606", "IM2607", "IM2608", "IM2609"] # 中证1000
},
"GEM": { # 广州期货交易所
"SI": ["SI2603", "SI2604", "SI2605", "SI2606", "SI2607", "SI2608", "SI2609"], # 工业硅
"SP": ["SP2603", "SP2604", "SP2605", "SP2606", "SP2607", "SP2608", "SP2609"] # 多晶硅
}
}
return symbols_by_exchange
def get_contract_months(self, product_code: str) -> List[str]:
"""获取合约的所有月份
Args:
product_code: 品种代码 "CU"
Returns:
List[str]: 该品种的所有合约月份列表
"""
# 本地枚举的合约月份
contract_months = ["2603", "2604", "2605", "2606", "2607", "2608", "2609"]
# 生成完整的合约代码
return [f"{product_code}{month}" for month in contract_months]
# 导入numpy # 导入numpy
import numpy as np import numpy as np

@ -2,8 +2,7 @@ numpy==1.26.4
pandas==2.2.1 pandas==2.2.1
matplotlib==3.8.3 matplotlib==3.8.3
scikit-learn==1.4.0 scikit-learn==1.4.0
tqsdk==1.6.3 rqdatac==2.0.0
requests==2.31.0
python-dotenv==1.0.0 python-dotenv==1.0.0
APScheduler==3.10.4 APScheduler==3.10.4
pytest==7.4.4 pytest==7.4.4

@ -0,0 +1,122 @@
#!/usr/bin/env python3
# 测试通过配置文件切换数据源
import os
import sys
# 确保能导入项目模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 从配置文件加载配置
from config import (
DATA_ADAPTER_TYPE,
TQSDK_USERNAME,
TQSDK_PASSWORD,
RQDATA_USERNAME,
RQDATA_PASSWORD,
DEBUG
)
# 设置环境变量
def setup_environment():
"""根据配置文件设置环境变量"""
print("=== 设置环境变量 ===")
# 设置数据源类型
os.environ["DATA_ADAPTER_TYPE"] = DATA_ADAPTER_TYPE
print(f"数据源类型: {DATA_ADAPTER_TYPE}")
# 设置TQSDK账号
if TQSDK_USERNAME:
os.environ["TQSDK_USERNAME"] = TQSDK_USERNAME
os.environ["TQSDK_PASSWORD"] = TQSDK_PASSWORD
print("TQSDK账号: 已配置")
else:
print("TQSDK账号: 未配置(将使用模拟数据)")
# 设置RQData账号
if RQDATA_USERNAME:
os.environ["RQDATA_USERNAME"] = RQDATA_USERNAME
os.environ["RQDATA_PASSWORD"] = RQDATA_PASSWORD
print("RQData账号: 已配置")
else:
print("RQData账号: 未配置(将使用模拟数据)")
# 设置调试模式
if DEBUG:
os.environ["DEBUG"] = "True"
print("调试模式: 开启")
else:
print("调试模式: 关闭")
print("环境变量设置完成!\n")
# 测试数据源连接
def test_data_source():
"""测试数据源连接和基本功能"""
print("=== 测试数据源功能 ===")
try:
from qihuo_analyzer.data.data_fetcher import DataFetcher
# 创建数据获取器实例
print("创建DataFetcher实例...")
fetcher = DataFetcher()
# 查看使用的适配器
adapter_name = fetcher.adapter.__class__.__name__
print(f"当前使用的适配器: {adapter_name}")
# 测试获取品种列表
print("\n测试获取品种列表...")
symbols = fetcher.get_all_symbols()
if symbols:
print(f"成功获取 {len(symbols)} 个品种")
print("前10个品种:", symbols[:10])
else:
print("获取品种列表失败")
# 测试获取K线数据
print("\n测试获取K线数据...")
test_symbol = symbols[0] if symbols else "CU2603"
kline_data = fetcher.get_kline_data(
symbol=test_symbol,
duration="1h", # 1小时
count=50
)
if kline_data is not None:
print(f"成功获取 {len(kline_data)} 条K线数据")
print("数据示例:")
print(kline_data.head())
else:
print("获取K线数据失败")
print("\n=== 测试完成 ===")
return True
except Exception as e:
print(f"测试失败: {e}")
import traceback
traceback.print_exc()
return False
# 主函数
def main():
"""主测试函数"""
print("开始测试通过配置文件切换数据源...\n")
# 1. 设置环境变量
setup_environment()
# 2. 测试数据源
success = test_data_source()
if success:
print("\n✓ 数据源切换测试成功!")
print(f"✓ 当前使用的数据源类型: {DATA_ADAPTER_TYPE}")
else:
print("\n✗ 数据源切换测试失败!")
if __name__ == "__main__":
main()

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# 测试数据适配器
from qihuo_analyzer.data.api_adapters import DataAdapterFactory
def test_adapter(adapter_type):
"""测试数据适配器
Args:
adapter_type: 适配器类型可选值'tqsdk', 'rqdata'
"""
print(f"\n=== 测试 {adapter_type} 适配器 ===")
# 创建适配器
adapter = DataAdapterFactory.create_adapter(adapter_type)
# 连接API
print("连接API...")
connected = adapter.connect()
print(f"连接结果: {'成功' if connected else '失败'}")
if connected:
# 测试获取K线数据
print("\n测试获取K线数据...")
kline_data = adapter.get_kline_data('CU2603', '1d', 10)
if kline_data is not None:
print(f"成功获取K线数据数据长度: {len(kline_data)}")
print(kline_data.head())
else:
print("获取K线数据失败")
# 测试获取Tick数据
print("\n测试获取Tick数据...")
tick_data = adapter.get_tick_data('CU2603', 100)
if tick_data is not None:
print(f"成功获取Tick数据数据长度: {len(tick_data)}")
print(tick_data.head())
else:
print("获取Tick数据失败")
# 测试获取合约信息
print("\n测试获取合约信息...")
contract_info = adapter.get_contract_info('CU2603')
if contract_info is not None:
print("成功获取合约信息:")
for key, value in contract_info.items():
print(f"{key}: {value}")
else:
print("获取合约信息失败")
# 测试批量获取市场数据
print("\n测试批量获取市场数据...")
market_data = adapter.get_market_data(['CU2603', 'AL2603'])
if market_data:
print("成功获取市场数据:")
for symbol, data in market_data.items():
print(f"{symbol}: {data}")
else:
print("获取市场数据失败")
# 测试获取所有品种列表
print("\n测试获取所有品种列表...")
symbols = adapter.get_all_symbols()
if symbols:
print(f"成功获取品种列表,共{len(symbols)}个品种")
print(f"前10个品种: {symbols[:10]}")
else:
print("获取品种列表失败")
# 断开连接
print("\n断开连接...")
adapter.disconnect()
else:
print("API连接失败跳过测试")
if __name__ == "__main__":
# 测试TQSDK适配器
test_adapter('tqsdk')
# 测试RQData适配器
test_adapter('rqdata')
print("\n=== 测试完成 ===")

@ -0,0 +1,123 @@
#!/usr/bin/env python3
# 测试枚举合约数据
import os
import sys
# 确保能导入项目模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from qihuo_analyzer.data.data_fetcher import DataFetcher
def test_enum_symbols():
"""测试枚举合约数据"""
print("=== 测试枚举合约数据 ===")
# 创建DataFetcher实例
fetcher = DataFetcher()
# 测试获取所有品种列表
print("\n1. 测试获取所有品种列表:")
symbols = fetcher.get_all_symbols()
print(f"成功获取 {len(symbols)} 个品种")
print("所有品种:", symbols)
# 测试获取按交易所划分的品种列表
print("\n2. 测试获取按交易所划分的品种列表:")
symbols_by_exchange = fetcher.get_all_symbols_by_exchange()
for exchange, products in symbols_by_exchange.items():
print(f"\n交易所: {exchange}")
print(f"品种数量: {len(products)}")
for product, contracts in products.items():
print(f" {product}: {contracts[:3]}... (共{len(contracts)}个合约)")
# 测试用户指定的品种是否都存在
print("\n3. 测试用户指定的品种是否都存在:")
specified_products = [
"", "", "", "", "", "玻璃", "烧碱", "纯碱", "焦煤",
"螺纹钢", "氧化铝", "甲醇", "PVC", "燃油", "原油", "", "棕榈油",
"碳酸锂", "工业硅", "橡胶", "合成橡胶", "", "20号胶", "多晶硅",
"中证1000", "中证500", "低硫燃油", "上证50"
]
# 品种中文名称映射
product_name_map = {
'AU': '',
'AG': '',
'CU': '',
'NI': '',
'SN': '',
'FG': '玻璃',
'LY': '烧碱',
'SA': '纯碱',
'JM': '焦煤',
'RB': '螺纹钢',
'ALO': '氧化铝',
'MA': '甲醇',
'V': 'PVC',
'FU': '燃油',
'SC': '原油',
'AL': '',
'P': '棕榈油',
'LI': '碳酸锂',
'SI': '工业硅',
'RU': '橡胶',
'BR': '合成橡胶',
'ZN': '',
'NR': '20号胶',
'SP': '多晶硅',
'IM': '中证1000',
'IC': '中证500',
'LU': '低硫燃油',
'IH': '上证50'
}
# 检查每个指定的品种
found_products = []
missing_products = []
for product_code, product_name in product_name_map.items():
# 检查是否在模拟品种列表中
mock_symbols = fetcher._get_mock_all_symbols()
product_found = any(symbol.startswith(product_code) for symbol in mock_symbols)
# 检查是否在按交易所划分的列表中
exchange_found = False
for exchange, products in symbols_by_exchange.items():
if product_code in products:
exchange_found = True
break
if product_found or exchange_found:
found_products.append(product_name)
print(f"✓ 找到: {product_name} ({product_code})")
else:
missing_products.append(product_name)
print(f"✗ 缺失: {product_name} ({product_code})")
print(f"\n4. 检查结果:")
print(f"找到的品种: {len(found_products)}/{len(specified_products)}")
print(f"缺失的品种: {len(missing_products)}/{len(specified_products)}")
if missing_products:
print(f"缺失的品种: {missing_products}")
else:
print("✓ 所有用户指定的品种都已找到!")
# 测试获取K线数据
print("\n5. 测试获取K线数据:")
test_symbols = ['AU2603', 'AG2603', 'CU2603', 'NI2603', 'SI2603']
for symbol in test_symbols:
try:
data = fetcher.get_kline_data(symbol, "1h", 10)
if data is not None:
print(f"✓ 成功获取 {symbol} 的K线数据 ({len(data)}条)")
else:
print(f"✗ 无法获取 {symbol} 的K线数据")
except Exception as e:
print(f"✗ 获取 {symbol} 数据失败: {e}")
print("\n=== 测试完成 ===")
if __name__ == "__main__":
test_enum_symbols()

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# 测试铁矿石合约代码修复
import os
import sys
# 确保能导入项目模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
from qihuo_analyzer.data.data_fetcher import DataFetcher
def test_iron_ore_contract():
"""测试铁矿石合约代码处理"""
print("=== 测试铁矿石合约代码修复 ===")
# 测试TQSDK适配器的合约代码转换
print("\n1. 测试TQSDK适配器合约代码转换:")
adapter = TqSdkAdapter()
test_symbols = [
"I2603", # 铁矿石
"J2603", # 焦炭
"JM2603", # 焦煤
"CU2603", # 铜
"AL2603", # 铝
"AU2603", # 黄金
"AG2603", # 白银
"RB2603", # 螺纹钢
"P2603", # 棕榈油
"V2603", # PVC
"MA2603", # 甲醇
"FG2603", # 玻璃
"SA2603", # 纯碱
"LY2603", # 烧碱
"LI2603", # 碳酸锂
"SI2603", # 工业硅
"SP2603", # 多晶硅
"RU2603", # 橡胶
"NR2603", # 20号胶
"FU2603", # 燃油
"SC2603", # 原油
"LU2603", # 低硫燃油
"ALO2603",# 氧化铝
"BR2603", # 合成橡胶
]
for symbol in test_symbols:
try:
tq_symbol = adapter._convert_symbol(symbol)
print(f"{symbol}{tq_symbol}")
except Exception as e:
print(f"{symbol} → 转换失败: {e}")
# 测试DataFetcher获取铁矿石数据
print("\n2. 测试DataFetcher获取铁矿石数据:")
fetcher = DataFetcher()
try:
# 测试获取铁矿石K线数据
print("测试获取 I2603 的K线数据...")
kline_data = fetcher.get_kline_data("I2603", "1h", 10)
if kline_data is not None:
print(f"✓ 成功获取 {len(kline_data)} 条铁矿石K线数据")
print("数据示例:")
print(kline_data.head())
else:
print("✗ 无法获取铁矿石K线数据")
except Exception as e:
print(f"✗ 获取铁矿石数据失败: {e}")
# 测试其他单字符品种
print("\n3. 测试其他单字符品种:")
single_char_symbols = ["I2603", "J2603", "P2603", "C2603", "L2603"]
for symbol in single_char_symbols:
try:
print(f"测试获取 {symbol} 的K线数据...")
kline_data = fetcher.get_kline_data(symbol, "1h", 5)
if kline_data is not None:
print(f"✓ 成功获取 {symbol} 的K线数据")
else:
print(f"✗ 无法获取 {symbol} 的K线数据")
except Exception as e:
print(f"✗ 获取 {symbol} 数据失败: {e}")
print("\n=== 测试完成 ===")
if __name__ == "__main__":
test_iron_ore_contract()

@ -0,0 +1,81 @@
#!/usr/bin/env python3
# 测试TQSDK适配器的_get_mock_all_symbols方法
import os
import sys
# 确保能导入项目模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
def test_mock_all_symbols():
"""测试_get_mock_all_symbols方法是否返回exchange_map中映射的所有品种"""
print("=== 测试TQSDK适配器_get_mock_all_symbols方法 ===")
# 创建TQSDK适配器实例
adapter = TqSdkAdapter()
# 获取模拟品种列表
mock_symbols = adapter._get_mock_all_symbols()
print(f"\n1. 测试结果:")
print(f"模拟品种列表长度: {len(mock_symbols)}")
print(f"exchange_map长度: {len(adapter.exchange_map)}")
print(f"两者长度是否一致: {len(mock_symbols) == len(adapter.exchange_map)}")
print("\n2. 模拟品种列表:")
print(sorted(mock_symbols))
print("\n3. exchange_map中的品种:")
print(sorted(adapter.exchange_map.keys()))
# 验证所有exchange_map中的品种都在模拟列表中
print("\n4. 验证所有exchange_map中的品种都在模拟列表中:")
missing_symbols = []
for product_code in adapter.exchange_map:
expected_symbol = f"{product_code}2603"
if expected_symbol not in mock_symbols:
missing_symbols.append(expected_symbol)
if missing_symbols:
print(f"✗ 缺失的品种: {missing_symbols}")
else:
print("✓ 所有exchange_map中的品种都在模拟列表中")
# 验证模拟列表中的品种都在exchange_map中
print("\n5. 验证模拟列表中的品种都在exchange_map中:")
invalid_symbols = []
for symbol in mock_symbols:
# 提取品种代码
if len(symbol) == 5:
# 3字符品种代码
product_code = symbol[:3]
elif len(symbol) == 4:
# 2字符或1字符品种代码
# 先尝试2字符
product_code = symbol[:2]
if product_code not in adapter.exchange_map:
# 尝试1字符
product_code = symbol[:1]
else:
product_code = symbol[:2]
if product_code not in adapter.exchange_map:
invalid_symbols.append(symbol)
if invalid_symbols:
print(f"✗ 无效的品种: {invalid_symbols}")
else:
print("✓ 模拟列表中的所有品种都在exchange_map中")
# 测试get_all_symbols方法
print("\n6. 测试get_all_symbols方法:")
all_symbols = adapter.get_all_symbols()
print(f"get_all_symbols返回长度: {len(all_symbols)}")
print(f"与模拟列表长度是否一致: {len(all_symbols) == len(mock_symbols)}")
print("\n=== 测试完成 ===")
if __name__ == "__main__":
test_mock_all_symbols()

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# 测试TQSDK合约代码格式修复
import os
import sys
# 确保能导入项目模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
from qihuo_analyzer.data.data_fetcher import DataFetcher
def test_tq_symbol_format():
"""测试TQSDK合约代码格式"""
print("=== 测试TQSDK合约代码格式修复 ===")
# 测试TQSDK适配器的合约代码转换
print("\n1. 测试TQSDK适配器合约代码转换:")
adapter = TqSdkAdapter()
test_symbols = [
"TA2603", # PTA
"CF2603", # 棉花
"SR2603", # 白糖
"MA2603", # 甲醇
"ZC2603", # 动力煤
"FG2603", # 玻璃
"RM2603", # 菜籽粕
"OI2603", # 菜籽油
"SA2603", # 纯碱
"LY2603", # 烧碱
"CU2603", # 铜
"AL2603", # 铝
"ZN2603", # 锌
"PB2603", # 铅
"NI2603", # 镍
"SN2603", # 锡
"AU2603", # 黄金
"AG2603", # 白银
"RB2603", # 螺纹钢
"HC2603", # 热轧卷板
"BU2603", # 沥青
"RU2603", # 橡胶
"FU2603", # 燃油
"I2603", # 铁矿石
"J2603", # 焦炭
"JM2603", # 焦煤
"A2603", # 大豆
"B2603", # 豆粕
"M2603", # 豆粕
"Y2603", # 豆油
"P2603", # 棕榈油
"C2603", # 玉米
"CS2603", # 玉米淀粉
"L2603", # 聚乙烯
"V2603", # 聚氯乙烯
"PP2603", # 聚丙烯
]
for symbol in test_symbols:
try:
tq_symbol = adapter._convert_symbol(symbol)
print(f"{symbol}{tq_symbol}")
except Exception as e:
print(f"{symbol} → 转换失败: {e}")
# 测试DataFetcher获取PTA数据
print("\n2. 测试DataFetcher获取PTA数据:")
fetcher = DataFetcher()
try:
# 测试获取PTA K线数据
print("测试获取 TA2603 的K线数据...")
kline_data = fetcher.get_kline_data("TA2603", "1h", 10)
if kline_data is not None:
print(f"✓ 成功获取 {len(kline_data)} 条PTA K线数据")
print("数据示例:")
print(kline_data.head())
else:
print("✗ 无法获取PTA K线数据")
except Exception as e:
print(f"✗ 获取PTA数据失败: {e}")
# 测试获取其他郑州商品交易所数据
print("\n3. 测试获取其他郑州商品交易所数据:")
czce_symbols = ["CF2603", "SR2603", "MA2603", "FG2603"]
for symbol in czce_symbols:
try:
print(f"测试获取 {symbol} 的K线数据...")
kline_data = fetcher.get_kline_data(symbol, "1h", 5)
if kline_data is not None:
print(f"✓ 成功获取 {symbol} 的K线数据")
else:
print(f"✗ 无法获取 {symbol} 的K线数据")
except Exception as e:
print(f"✗ 获取 {symbol} 数据失败: {e}")
print("\n=== 测试完成 ===")
if __name__ == "__main__":
test_tq_symbol_format()

@ -0,0 +1,49 @@
self.exchange_map = {
'AU': 'SHFE', # 黄金 - 上海期货交易所
'AG': 'SHFE', # 白银 - 上海期货交易所
'CU': 'SHFE', # 铜 - 上海期货交易所
'NI': 'SHFE', # 镍 - 上海期货交易所
'SN': 'SHFE', # 锡 - 上海期货交易所
'FG': 'CZCE', # 玻璃 - 郑州商品交易所
'LY': 'CZCE', # 烧碱 - 郑州商品交易所
'SA': 'CZCE', # 纯碱 - 郑州商品交易所
'JM': 'DCE', # 焦煤 - 大连商品交易所
'RB': 'SHFE', # 螺纹钢 - 上海期货交易所
'ALO': 'SHFE', # 氧化铝 - 上海期货交易所
'MA': 'CZCE', # 甲醇 - 郑州商品交易所
'V': 'DCE', # PVC - 大连商品交易所
'FU': 'SHFE', # 燃油 - 上海期货交易所
'SC': 'INE', # 原油 - 上海国际能源交易中心
'AL': 'SHFE', # 铝 - 上海期货交易所
'P': 'DCE', # 棕榈油 - 大连商品交易所
'LI': 'SHFE', # 碳酸锂 - 上海期货交易所
'SI': 'GEM', # 工业硅 - 广州期货交易所
'RU': 'SHFE', # 橡胶 - 上海期货交易所
'BR': 'DCE', # 合成橡胶 - 大连商品交易所
'ZN': 'SHFE', # 锌 - 上海期货交易所
'NR': 'SHFE', # 20号胶 - 上海期货交易所
'SP': 'GEM', # 多晶硅 - 广州期货交易所
'IM': 'CFFEX', # 中证1000 - 中国金融期货交易所
'IC': 'CFFEX', # 中证500 - 中国金融期货交易所
'LU': 'INE', # 低硫燃油 - 上海国际能源交易中心
'IH': 'CFFEX', # 上证50 - 中国金融期货交易所
'HC': 'SHFE', # 热轧卷板 - 上海期货交易所
'BU': 'SHFE', # 沥青 - 上海期货交易所
'PB': 'SHFE', # 铅 - 上海期货交易所
'I': 'DCE', # 铁矿石 - 大连商品交易所
'J': 'DCE', # 焦炭 - 大连商品交易所
'A': 'DCE', # 大豆 - 大连商品交易所
'B': 'DCE', # 豆粕 - 大连商品交易所
'M': 'DCE', # 豆粕 - 大连商品交易所
'Y': 'DCE', # 豆油 - 大连商品交易所
'C': 'DCE', # 玉米 - 大连商品交易所
'CS': 'DCE', # 玉米淀粉 - 大连商品交易所
'L': 'DCE', # 聚乙烯 - 大连商品交易所
'PP': 'DCE', # 聚丙烯 - 大连商品交易所
'TA': 'CZCE', # PTA - 郑州商品交易所
'CF': 'CZCE', # 棉花 - 郑州商品交易所
'SR': 'CZCE', # 白糖 - 郑州商品交易所
'ZC': 'CZCE', # 动力煤 - 郑州商品交易所
'RM': 'CZCE', # 菜籽粕 - 郑州商品交易所
'OI': 'CZCE', # 菜籽油 - 郑州商品交易所
}

@ -0,0 +1,295 @@
# 数据源切换指南
本指南详细说明了如何在AlphaFutures系统中切换不同的数据源TQSDK、RQData等
## 方法一:通过环境变量配置(推荐)
### 1. Windows系统环境变量配置
1. **打开系统属性**
- 右键点击"此电脑" → 选择"属性"
- 点击"高级系统设置"
2. **进入环境变量设置**
- 点击"环境变量"按钮
3. **添加/修改系统变量**
- 在"系统变量"区域点击"新建"(如果已存在则点击"编辑"
- 变量名:`DATA_ADAPTER_TYPE`
- 变量值:
- `tqsdk` - 使用TQSDK数据源
- `rqdata` - 使用RQData数据源
4. **设置账号信息**(可选):
- **TQSDK账号**
- 新建变量 `TQSDK_USERNAME`值为你的TQSDK用户名
- 新建变量 `TQSDK_PASSWORD`值为你的TQSDK密码
- **RQData账号**
- 新建变量 `RQDATA_USERNAME`值为你的RQData用户名
- 新建变量 `RQDATA_PASSWORD`值为你的RQData密码
5. **生效配置**
- 点击"确定"保存所有设置
- **重启应用程序**:环境变量变更需要重启应用才能生效
### 2. Linux/Mac系统环境变量配置
1. **编辑配置文件**
```bash
# 对于bash用户
nano ~/.bashrc
# 对于zsh用户
nano ~/.zshrc
```
2. **添加环境变量**
```bash
# 设置数据源类型
export DATA_ADAPTER_TYPE="rqdata"
# 设置RQData账号可选
export RQDATA_USERNAME="your_username"
export RQDATA_PASSWORD="your_password"
```
3. **生效配置**
```bash
# 对于bash用户
source ~/.bashrc
# 对于zsh用户
source ~/.zshrc
```
## 方法二:在代码中临时切换
### 1. 临时设置环境变量
```python
import os
# 临时切换到RQData数据源
os.environ["DATA_ADAPTER_TYPE"] = "rqdata"
# 设置RQData账号可选
os.environ["RQDATA_USERNAME"] = "your_username"
os.environ["RQDATA_PASSWORD"] = "your_password"
# 然后创建DataFetcher实例
from qihuo_analyzer.data.data_fetcher import DataFetcher
fetcher = DataFetcher()
# 现在fetcher会使用RQData数据源
kline_data = fetcher.get_kline_data("SHFE.rb2409", 3600, start_time, end_time)
```
### 2. 直接指定适配器
```python
from qihuo_analyzer.data.api_adapters.adapter_factory import DataAdapterFactory
# 直接创建RQData适配器
rqdata_adapter = DataAdapterFactory.create_adapter("rqdata")
rqdata_adapter.connect()
# 使用RQData获取数据
data = rqdata_adapter.get_kline_data("SHFE.rb2409", 3600, start_time, end_time)
# 关闭连接
rqdata_adapter.disconnect()
# 切换到TQSDK适配器
tqsdk_adapter = DataAdapterFactory.create_adapter("tqsdk")
tqsdk_adapter.connect()
# 使用TQSDK获取数据
data = tqsdk_adapter.get_kline_data("SHFE.rb2409", 3600, start_time, end_time)
# 关闭连接
tqsdk_adapter.disconnect()
```
## 方法三:在配置文件中设置
如果系统支持配置文件,可以在配置文件中添加数据源设置:
### 1. 创建配置文件
在项目根目录创建 `config.py` 文件:
```python
# config.py
DATA_ADAPTER_TYPE = "rqdata" # 可选值: tqsdk, rqdata
# RQData账号配置可选
RQDATA_USERNAME = "your_username"
RQDATA_PASSWORD = "your_password"
# TQSDK账号配置可选
TQSDK_USERNAME = "your_username"
TQSDK_PASSWORD = "your_password"
```
### 2. 在代码中加载配置
```python
import os
from config import DATA_ADAPTER_TYPE, RQDATA_USERNAME, RQDATA_PASSWORD, TQSDK_USERNAME, TQSDK_PASSWORD
# 设置环境变量
os.environ["DATA_ADAPTER_TYPE"] = DATA_ADAPTER_TYPE
# 设置账号信息
if RQDATA_USERNAME:
os.environ["RQDATA_USERNAME"] = RQDATA_USERNAME
if RQDATA_PASSWORD:
os.environ["RQDATA_PASSWORD"] = RQDATA_PASSWORD
if TQSDK_USERNAME:
os.environ["TQSDK_USERNAME"] = TQSDK_USERNAME
if TQSDK_PASSWORD:
os.environ["TQSDK_PASSWORD"] = TQSDK_PASSWORD
# 创建DataFetcher实例
from qihuo_analyzer.data.data_fetcher import DataFetcher
fetcher = DataFetcher()
```
## 查看当前使用的数据源
### 1. 代码中查看
```python
from qihuo_analyzer.data.data_fetcher import DataFetcher
# 创建DataFetcher实例
fetcher = DataFetcher()
# 查看当前使用的适配器类型
print(f"当前使用的数据源: {fetcher.adapter.__class__.__name__}")
# 查看适配器配置信息
print(f"适配器配置: {fetcher.adapter.config}")
```
### 2. 检查环境变量
```python
import os
# 查看当前数据源类型
adapter_type = os.environ.get("DATA_ADAPTER_TYPE", "tqsdk")
print(f"当前配置的数据源类型: {adapter_type}")
# 查看账号配置
if adapter_type == "rqdata":
username = os.environ.get("RQDATA_USERNAME", "未配置")
print(f"RQData账号: {username}")
elif adapter_type == "tqsdk":
username = os.environ.get("TQSDK_USERNAME", "未配置")
print(f"TQSDK账号: {username}")
```
## 切换数据源的注意事项
### 1. 账号配置
- **切换到TQSDK**需要配置TQSDK账号密码否则使用模拟数据
- **切换到RQData**需要配置RQData账号密码否则使用模拟数据
- **未配置账号**:系统会自动使用模拟数据,确保系统正常运行
### 2. 数据一致性
- 不同数据源的数据格式可能略有差异,但统一接口会确保返回格式一致
- 历史数据的时间范围和精度可能因数据源而异
- 实时数据的更新频率可能不同
### 3. 性能考虑
- **TQSDK**:适合实时数据和高频交易策略
- **RQData**:适合历史数据回测和研究分析
- 选择适合你使用场景的数据源
### 4. 错误处理
- 切换数据源后,建议测试基本功能确保连接正常
- 如遇连接问题,检查账号配置和网络连接
- 系统会自动处理API错误并使用模拟数据作为降级方案
## 常见问题解决
### Q: 切换数据源后连接失败
**A:**
1. 检查账号密码是否正确
2. 检查网络连接是否正常
3. 检查API服务是否正常运行
4. 尝试重启应用程序
### Q: 如何在运行时动态切换数据源
**A:**
```python
import os
from qihuo_analyzer.data.data_fetcher import DataFetcher
# 先使用TQSDK
os.environ["DATA_ADAPTER_TYPE"] = "tqsdk"
fetcher1 = DataFetcher()
# 再使用RQData
os.environ["DATA_ADAPTER_TYPE"] = "rqdata"
fetcher2 = DataFetcher()
# 现在fetcher1使用TQSDKfetcher2使用RQData
```
### Q: 数据源切换后数据格式不一致
**A:**
统一接口已处理数据格式问题,确保返回格式一致。如果遇到问题,请检查:
1. 确认使用的是最新版本的统一接口
2. 检查数据获取参数是否正确
3. 查看日志中的错误信息
## 示例:完整的数据源切换流程
### 场景从TQSDK切换到RQData
1. **设置环境变量**
- `DATA_ADAPTER_TYPE` = "rqdata"
- `RQDATA_USERNAME` = "your_rqdata_username"
- `RQDATA_PASSWORD` = "your_rqdata_password"
2. **重启应用**:确保环境变量生效
3. **验证切换**
```python
from qihuo_analyzer.data.data_fetcher import DataFetcher
# 创建DataFetcher实例
fetcher = DataFetcher()
# 测试数据获取
try:
# 获取K线数据
data = fetcher.get_kline_data(
symbol="SHFE.rb2409",
interval=3600,
start_time="2024-01-01 00:00:00",
end_time="2024-01-02 00:00:00"
)
print("数据获取成功!")
print(f"获取到 {len(data)} 条数据")
print(f"当前使用的数据源: {fetcher.adapter.__class__.__name__}")
except Exception as e:
print(f"数据获取失败: {e}")
```
4. **确认切换成功**
- 查看输出中的"当前使用的数据源"信息
- 检查数据获取是否正常
## 总结
通过以上方法,你可以轻松在不同的数据源之间切换,选择最适合你使用场景的数据源。统一接口的设计确保了切换数据源不会影响业务逻辑,为系统提供了更大的灵活性。

@ -0,0 +1,354 @@
# 统一数据获取接口开发文档
## 1. 概述
本开发文档描述了AlphaFutures系统中统一数据获取接口的设计与实现。该接口采用适配器设计模式支持多种数据源TQSDK、RQData等为系统提供统一的数据获取出口实现业务逻辑与数据获取的解耦。
### 1.1 设计目标
- **统一接口**为所有数据源提供一致的API接口
- **可扩展性**:支持轻松添加新的数据源适配器
- **配置灵活**:通过配置选择不同的数据源
- **容错处理**:包含错误处理和降级机制
- **模拟数据**:支持无账号时的模拟数据功能
### 1.2 技术栈
- Python 3.8+
- 抽象基类ABC
- 工厂模式
- 依赖注入
- 环境变量配置
## 2. 架构设计
### 2.1 整体架构
```
业务逻辑层
| 依赖注入
统一数据获取层 (DataFetcher)
| 工厂模式
适配器抽象层 (BaseDataAdapter)
| 继承实现
具体适配器实现
├── TQSdkAdapter
├── RQDataAdapter
└── 其他适配器...
```
### 2.2 核心设计模式
1. **适配器模式**:将不同数据源的接口转换为统一接口
2. **工厂模式**:根据配置创建对应的适配器实例
3. **依赖注入**:业务逻辑通过统一接口获取数据,不直接依赖具体实现
## 3. 核心组件
### 3.1 抽象基类 (BaseDataAdapter)
定义了所有数据适配器必须实现的接口方法,确保一致性。
**文件路径**`qihuo_analyzer/data/api_adapters/base_adapter.py`
**核心方法**
- `connect()` - 连接数据源
- `disconnect()` - 断开连接
- `get_kline_data()` - 获取K线数据
- `get_tick_data()` - 获取tick数据
- `get_contract_info()` - 获取合约信息
- `get_market_data()` - 获取市场数据
- `get_all_symbols()` - 获取所有品种列表
### 3.2 具体适配器
#### 3.2.1 TQSDK适配器
**文件路径**`qihuo_analyzer/data/api_adapters/tqsdk_adapter.py`
实现了TQSDK的所有接口方法包含错误处理和模拟数据支持。
#### 3.2.2 RQData适配器
**文件路径**`qihuo_analyzer/data/api_adapters/rqdata_adapter.py`
实现了RQData的所有接口方法包含错误处理和模拟数据支持。
### 3.3 适配器工厂
**文件路径**`qihuo_analyzer/data/api_adapters/adapter_factory.py`
根据配置创建对应的适配器实例,支持通过环境变量选择数据源类型。
### 3.4 统一数据获取器
**文件路径**`qihuo_analyzer/data/data_fetcher.py`
使用统一接口获取数据替代直接API调用为业务逻辑提供一致的数据获取方式。
## 4. 实现细节
### 4.1 配置管理
#### 4.1.1 环境变量配置
| 环境变量 | 说明 | 默认值 |
|---------|------|--------|
| DATA_ADAPTER_TYPE | 数据适配器类型 (tqsdk/rqdata) | tqsdk |
| TQSDK_USERNAME | TQSDK账号 | 无 |
| TQSDK_PASSWORD | TQSDK密码 | 无 |
| RQDATA_USERNAME | RQData账号 | 无 |
| RQDATA_PASSWORD | RQData密码 | 无 |
#### 4.1.2 账号配置
- **TQSDK**:设置`TQSDK_USERNAME`和`TQSDK_PASSWORD`环境变量
- **RQData**:设置`RQDATA_USERNAME`和`RQDATA_PASSWORD`环境变量
- **未配置账号**:自动使用模拟数据
### 4.2 错误处理
所有适配器都包含完整的错误处理机制:
1. **连接错误**:捕获连接异常,返回错误信息并使用模拟数据
2. **数据获取错误**捕获API调用异常返回错误信息并使用模拟数据
3. **参数错误**:验证输入参数,返回错误信息
### 4.3 模拟数据
当API连接失败或未配置账号时自动使用模拟数据
- **K线数据**:生成随机波动的价格数据
- **Tick数据**:生成随机的成交数据
- **合约信息**:返回基本合约信息
- **市场数据**:返回模拟的市场行情
- **品种列表**:返回内置的品种列表
### 4.4 数据格式统一
所有适配器返回的数据格式保持一致:
- **K线数据**:包含时间、开盘价、最高价、最低价、收盘价、成交量
- **Tick数据**:包含时间、最新价、成交量、持仓量、买价、买量、卖价、卖量
- **合约信息**:包含合约代码、交易所、品种类型、合约大小等
- **市场数据**:包含最新价、涨跌幅、成交量、持仓量等
- **品种列表**:包含品种代码、交易所、品种名称等
## 5. 使用方法
### 5.1 基本使用
```python
from qihuo_analyzer.data.data_fetcher import DataFetcher
# 创建数据获取器实例(自动使用配置的适配器)
fetcher = DataFetcher()
# 获取K线数据
kline_data = fetcher.get_kline_data(
symbol="SHFE.rb2409",
interval=3600, # 1小时
start_time="2024-01-01 00:00:00",
end_time="2024-01-02 00:00:00"
)
# 获取Tick数据
tick_data = fetcher.get_tick_data(
symbol="SHFE.rb2409",
start_time="2024-01-01 09:00:00",
end_time="2024-01-01 11:30:00"
)
# 获取合约信息
contract_info = fetcher.get_contract_info("SHFE.rb2409")
# 获取市场数据
market_data = fetcher.get_market_data(["SHFE.rb2409", "DCE.i2409"])
# 获取所有品种列表
symbols = fetcher.get_all_symbols()
```
### 5.2 手动指定适配器
```python
from qihuo_analyzer.data.api_adapters.adapter_factory import DataAdapterFactory
# 手动创建特定适配器
adapter = DataAdapterFactory.create_adapter("rqdata")
adapter.connect()
# 使用适配器获取数据
data = adapter.get_kline_data("SHFE.rb2409", 3600, start_time, end_time)
# 断开连接
adapter.disconnect()
```
## 6. 配置说明
### 6.1 Windows环境变量配置
1. **打开系统属性**:右键"此电脑" → "属性" → "高级系统设置"
2. **环境变量**:点击"环境变量"按钮
3. **添加系统变量**:在"系统变量"区域点击"新建"
4. **设置变量**
- 变量名:`DATA_ADAPTER_TYPE`
- 变量值:`tqsdk` 或 `rqdata`
5. **设置账号变量**(可选):
- TQSDK添加 `TQSDK_USERNAME``TQSDK_PASSWORD`
- RQData添加 `RQDATA_USERNAME``RQDATA_PASSWORD`
6. **重启应用**:配置生效需要重启应用程序
### 6.2 临时配置
在Python代码中临时设置环境变量
```python
import os
# 设置适配器类型
os.environ["DATA_ADAPTER_TYPE"] = "rqdata"
# 设置RQData账号
os.environ["RQDATA_USERNAME"] = "your_username"
os.environ["RQDATA_PASSWORD"] = "your_password"
# 然后创建DataFetcher实例
from qihuo_analyzer.data.data_fetcher import DataFetcher
fetcher = DataFetcher()
```
## 7. 测试验证
### 7.1 测试脚本
**文件路径**`test_data_adapters.py`
测试脚本验证了所有适配器的功能:
- 连接测试
- 数据获取测试
- 错误处理测试
- 模拟数据测试
### 7.2 运行测试
```bash
# 在项目根目录运行测试
python test_data_adapters.py
```
### 7.3 测试结果
测试输出示例:
```
=== 测试 tqsdk 适配器 ===
创建TQSDK数据适配器
连接API...
TQSDK账号密码未配置将使用模拟数据
连接结果: 失败
API连接失败跳过测试
=== 测试 rqdata 适配器 ===
创建RQData数据适配器
连接API...
RQData账号密码未配置将使用模拟数据
连接结果: 失败
API连接失败跳过测试
=== 测试完成 ===
```
## 8. 未来扩展
### 8.1 添加新适配器
要添加新的数据源适配器,只需:
1. **创建适配器类**:继承`BaseDataAdapter`
2. **实现接口方法**:实现所有抽象方法
3. **注册适配器**:在工厂类中添加适配器类型
**示例**
```python
# 在adapter_factory.py中添加
ADAPTERS = {
"tqsdk": TQSdkAdapter,
"rqdata": RQDataAdapter,
"new_adapter": NewAdapter # 新适配器
}
```
### 8.2 增强功能
未来可考虑的增强功能:
- **缓存机制**添加数据缓存减少重复API调用
- **多数据源融合**:支持从多个数据源获取数据并融合
- **数据源健康检查**:定期检查数据源可用性
- **数据质量监控**:监控数据完整性和准确性
- **异步数据获取**支持异步API调用提高性能
## 9. 故障排除
### 9.1 常见问题
1. **API连接失败**
- 检查账号配置是否正确
- 检查网络连接
- 检查API服务是否正常
2. **数据获取失败**
- 检查合约代码是否正确
- 检查时间范围是否合理
- 检查API权限是否足够
3. **适配器初始化失败**
- 检查环境变量配置
- 检查依赖包是否安装
- 检查Python版本是否兼容
### 9.2 日志与调试
- **日志级别**:可通过配置调整日志级别
- **调试模式**:设置环境变量`DEBUG=True`启用调试模式
- **错误信息**:详细的错误信息会记录到日志中
## 10. 依赖管理
### 10.1 核心依赖
| 依赖包 | 版本 | 用途 |
|--------|------|------|
| tqsdk | ^1.9.15 | TQSDK数据源支持 |
| rqdatac | ^2.0.0 | RQData数据源支持 |
| pandas | ^1.3.0 | 数据处理 |
| numpy | ^1.20.0 | 数值计算 |
### 10.2 安装依赖
```bash
# 安装依赖包
pip install -r requirements.txt
```
## 11. 总结
统一数据获取接口的实现成功解决了多数据源集成的问题为AlphaFutures系统提供了
- **灵活性**:支持多种数据源,可根据需要切换
- **可靠性**:包含错误处理和降级机制
- **可扩展性**:支持轻松添加新的数据源
- **一致性**:为业务逻辑提供统一的数据接口
- **可维护性**:代码结构清晰,易于维护和测试
该设计不仅满足了当前的需求,也为未来的功能扩展和性能优化奠定了基础。
Loading…
Cancel
Save