注意:需要先订阅行情才能获取五档数据
Args:
codes: 股票代码
Returns:
DataFrame: 包含五档买卖价和量的数据
字段包括:
- code: 股票代码
- lastPrice: 最新价
- bid1-bid5: 买一到买五价格
- ask1-ask5: 卖一到卖五价格
- bidVol1-bidVol5: 买一到买五量
- askVol1-askVol5: 卖一到卖五量
Raises:
ConnectionError: 连接失败
DataError: 数据获取失败
"""
if not self.xt:
raise ConnectionError("xtquant未正确导入,无法获取数据")
if not self._connected:
raise ConnectionError("数据服务未连接,请先调用init_data()并确保迅投客户端已启动")
codes = StockCodeUtils.normalize_codes(codes)
try:
# 先订阅行情(重要:必须先订阅才能获取五档数据)
if isinstance(codes, str):
codes_list = [codes]
else:
codes_list = codes
# 订阅每只股票的tick行情
print("[DEBUG] 开始订阅行情...")
for code in codes_list:
try:
# subscribe_quote 订阅行情接口
# period: 'tick' 分笔, '1d' 日线等
if hasattr(self.xt, 'subscribe_quote'):
result = self.xt.subscribe_quote(code, period='tick')
print(f"[DEBUG] {code} 订阅结果: {result}")
except Exception as e:
# 订阅失败不中断,继续尝试获取数据
print(f"[DEBUG] {code} 订阅异常: {e}")
# 等待让订阅生效并重试获取数据
import time
print("[DEBUG] 等待数据推送...")
time.sleep(2.0) # 增加到2秒
# 获取完整tick数据,带重试机制
tick_data = None
max_retries = 3
for attempt in range(max_retries):
tick_data = self.xt.get_full_tick(codes)
if tick_data:
# 检查是否有五档数据
has_order_book = False
for code, tick_info in tick_data.items():
if tick_info:
# 根据官方文档,字段名是 askPrice 和 bidPrice
ask_price = tick_info.get('askPrice')
bid_price = tick_info.get('bidPrice')
# 这些字段可能是数组或单个值
# 检查是否有非空值(数组、非None的数字都算有数据)
if ask_price is not None or bid_price is not None:
has_order_book = True
break
if has_order_book:
print(f"[DEBUG] 第{attempt + 1}次尝试成功获取到五档数据")
break
else:
print(f"[DEBUG] 第{attempt + 1}次尝试:五档数据为空,等待2秒后重试...")
if attempt < max_retries - 1:
time.sleep(2.0)
else:
print(f"[DEBUG] 第{attempt + 1}次尝试:无法获取tick数据")
if attempt < max_retries - 1:
time.sleep(2.0)
if not tick_data:
raise DataError("无法获取五档行情数据")
result_list = []
for code, tick_info in tick_data.items():
if tick_info:
# 获取askPrice和bidPrice - 根据文档这些可能是数组
ask_price = tick_info.get('askPrice', 0)
bid_price = tick_info.get('bidPrice', 0)
ask_vol = tick_info.get('askVol', 0)
bid_vol = tick_info.get('bidVol', 0)
# 处理askPrice和bidPrice可能是数组的情况
def extract_price_or_vol(value, index=0):
"""提取价格或量,支持数组或单个值"""
if hasattr(value, '__len__') and not isinstance(value, str):
# 是数组或列表
if len(value) > index:
return value[index]
return 0
# 是单个值
if index == 0:
return value if value else 0
return 0
# 提取五档行情数据
order_book_data = {
'code': code,
'lastPrice': tick_info.get('lastPrice', 0),
'open': tick_info.get('open', 0),
'high': tick_info.get('high', 0),
'low': tick_info.get('low', 0),
'preClose': tick_info.get('lastClose', 0),
'volume': tick_info.get('volume', 0),
'amount': tick_info.get('amount', 0),
'time': tick_info.get('time', 0),
# 五档买价 - 使用askPrice和bidPrice
'bid1': extract_price_or_vol(bid_price, 0),
'bid2': extract_price_or_vol(bid_price, 1),
'bid3': extract_price_or_vol(bid_price, 2),
'bid4': extract_price_or_vol(bid_price, 3),
'bid5': extract_price_or_vol(bid_price, 4),
# 五档卖价
'ask1': extract_price_or_vol(ask_price, 0),
'ask2': extract_price_or_vol(ask_price, 1),
'ask3': extract_price_or_vol(ask_price, 2),
'ask4': extract_price_or_vol(ask_price, 3),
'ask5': extract_price_or_vol(ask_price, 4),
# 五档买量
'bidVol1': extract_price_or_vol(bid_vol, 0),
'bidVol2': extract_price_or_vol(bid_vol, 1),
'bidVol3': extract_price_or_vol(bid_vol, 2),
'bidVol4': extract_price_or_vol(bid_vol, 3),
'bidVol5': extract_price_or_vol(bid_vol, 4),
# 五档卖量
'askVol1': extract_price_or_vol(ask_vol, 0),
'askVol2': extract_price_or_vol(ask_vol, 1),
'askVol3': extract_price_or_vol(ask_vol, 2),
'askVol4': extract_price_or_vol(ask_vol, 3),
'askVol5': extract_price_or_vol(ask_vol, 4),
}
result_list.append(order_book_data)
if not result_list:
raise DataError("未获取到有效的五档行情数据")
return pd.DataFrame(result_list)
except Exception as e:
if isinstance(e, (ConnectionError, DataError)):
raise
ErrorHandler.log_error(f"获取五档行情失败: {str(e)}")
raise DataError(f"获取五档行情失败: {str(e)}")
明明直接使用get_full_tick就可以获取5档盘口,为什么要先订阅一下再用get_full_tick??
def get_order_book(self, codes: Union[str, List[str]]) -> pd.DataFrame:
"""
获取五档行情数据(买卖盘口)