问题描述
早上通过get_full_tick,一直能用的数据突然报错。发现600039的lastClose全是NULL,无法处理。
tick数据提供都能出错,你们公司做的太烂了。而且取了几次,全都是这个原因。
解决方案
在量化交易实盘中,遇到接口返回 NULL 或 NaN(缺失值)是比较常见的情况。虽然底层数据源的偶发异常会给策略运行带来困扰,但作为一个成熟的量化策略,必须在代码层面做好充分的容错处理,以应对各种极端的数据异常情况。
针对您提到的 get_full_tick 接口获取 600039.SH 时 lastClose(昨收价)返回 NULL 的问题,以下是原因分析及代码层面的解决方案。
一、 为什么 lastClose 会返回 NULL?
- 交易所数据推送延迟或异常:在早盘集合竞价期间或刚开盘时,个别股票的 Tick 数据包可能存在字段缺失,导致平台接收到的原始数据中就没有昨收价。
- 除权除息日/停牌/复牌:如果该股票当天正好是除权除息日,或者处于停牌、临时停牌状态,数据源在处理复权基准价时可能会出现短暂的空值。
- 本地数据未同步:如果本地客户端的静态数据(如合约基础信息)未完全同步,也可能导致某些基础字段解析为 NULL。
二、 解决方案 1:在代码中增加容错处理(推荐)
在处理任何行情数据时,都不要假设数据是 100% 完美的。在提取 lastClose 参与计算前,必须进行 None 或 NaN 的判断。
import math
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
stock_code = '600039.SH'
tick_data = ContextInfo.get_full_tick([stock_code])
if stock_code in tick_data:
stock_tick = tick_data[stock_code]
last_close = stock_tick.get('lastClose', None)
# 容错处理:判断是否为 None 或 NaN
if last_close is None or math.isnan(last_close) or last_close == 0:
print(f"警告:{stock_code} 的 lastClose 数据异常 ({last_close}),跳过本次计算或使用替代方案。")
# 可以在这里 return 跳过,或者调用替代方案获取昨收价
return
# 正常逻辑
current_price = stock_tick.get('lastPrice', 0)
print(f"{stock_code} 最新价: {current_price}, 昨收价: {last_close}")
三、 解决方案 2:使用替代接口获取昨收价
如果 get_full_tick 中的 lastClose 缺失,但您的策略又强依赖昨收价(例如计算涨跌幅),可以通过 get_market_data_ex 或 get_history_data 获取该股票前一个交易日的收盘价作为替代。
def get_fallback_last_close(ContextInfo, stock_code):
"""
当 tick 数据缺失昨收价时,通过日线数据获取真实的昨收价
"""
# 获取最近2天的日线数据(包含今天和昨天)
# 注意:如果在盘中获取,最后一条是今天的未完成日线,倒数第二条是昨天的日线
df = ContextInfo.get_market_data_ex(
fields=['close'],
stock_code=[stock_code],
period='1d',
count=2,
dividend_type='none' # 根据需要选择复权方式
)
if stock_code in df and not df[stock_code].empty:
# 如果当前时间已经开盘,df会有两条数据,取第一条(昨天)的收盘价
if len(df[stock_code]) >= 2:
return df[stock_code]['close'].iloc[-2]
elif len(df[stock_code]) == 1:
# 如果只有一条,说明可能是盘前,这条就是昨天的
return df[stock_code]['close'].iloc[0]
return None
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
stock_code = '600039.SH'
tick_data = ContextInfo.get_full_tick([stock_code])
if stock_code in tick_data:
last_close = tick_data[stock_code].get('lastClose', None)
# 如果 lastClose 异常,启用替代方案
if last_close is None or last_close == 0 or last_close != last_close: # last_close != last_close 是判断 NaN 的简便方法
print(f"Tick数据 lastClose 异常,尝试从日线获取...")
last_close = get_fallback_last_close(ContextInfo, stock_code)
if last_close is None:
print("彻底无法获取昨收价,放弃本次交易逻辑。")
return
print(f"最终获取到的 {stock_code} 昨收价为: {last_close}")
四、 总结建议
- 防御性编程:在量化实盘中,任何字段(最新价、买卖盘、昨收等)都可能因为各种不可抗力出现异常值。策略中加入
if value is None or math.isnan(value):是必不可少的防御性编程习惯。 - 数据补充:请检查 QMT 客户端左上角的“数据管理”,确保该股票的基础数据和日线数据已经补充完整,这有助于底层接口正确计算和返回数据。