问题描述
下面,我对 xtdata.get_market_data_ex()函数进行了分析,发现内部也并没有对 int 数据类型进行显式的预处理。
xtdata.get_market_data_ex()在获取日线(period = "1d")数据时,流程大概是这样的:
xtdata.get_market_data_ex() ->
内部通过 period 分支将函数指向_get_market_data_ex_ori_221207()
if spec_period in {'1m', '5m', '15m', '30m', '60m', '1h', '1d', '1w', '1mon', '1q', '1hy', '1y'}:
return _get_market_data_ex_ori_221207(field_list, stock_list, spec_period, start_time, end_time, count, dividend_type, fill_data)
然后在_get_market_data_ex_ori_221207()函数内部最终向服务器请求的时 client.get_market_data3()
通过转向该函数的声明可以发现,该函数是经过 pybind c++ 静态编译完成的,我们无法查看原始代码和断点测试,但可以知道,返回的数据是 buffer。
ret = client.get_market_data3(field_list, stock_list, period, start_time, end_time, count, dividend_type, fill_data, 'v4', enable_read_from_local,
enable_read_from_server, debug_mode, data_dir)
result = {}
for stock, index, npdatas in ret:
data = {field: np.frombuffer(b, fi) for field, fi, b in npdatas}
result[stock] = pd.DataFrame(data=data, index=index)
return result
解决方案
QMT/迅投 get_market_data_ex 整数溢出问题解析
在使用 QMT 平台或迅投 xtdata 模块获取 A 股市场数据时,部分开发者会遇到数据异常(如出现负数或极小值),这通常是由于**整数溢出(Integer Overflow)**引起的。正如您所分析的,问题的根源在于底层数据库和 C++ 接口的数据类型定义。
1. 问题根源分析
在 A 股市场中,热门股票的单日成交量(Volume)或成交额(Amount)往往非常巨大。例如,某只股票单日成交量可能达到 30 亿股。
- 底层 C++ 限制:
client.get_market_data3是经过 pybind 封装的 C++ 底层函数。在 C++ 中,标准的有符号 32 位整数(int32)的最大值约为 21.47 亿($2^{31}-1$)。当成交量超过这个数值时,就会发生溢出,变成负数。 - Buffer 解析机制:在 Python 端,
xtdata或 QMT 内部通过np.frombuffer(b, fi)将 C++ 传回的内存 buffer 直接映射为 Numpy 数组。如果底层fi(数据类型描述符)被定义为 32 位整数,那么 Numpy 解析出来的数据就已经溢出了。
2. QMT 平台中的对应接口
在 QMT 平台的策略编辑器中,对应的接口是 ContextInfo.get_market_data_ex()。其返回的数据结构为字典,值为 pandas.DataFrame。
# QMT 平台获取数据示例
data = ContextInfo.get_market_data_ex(
['open', 'high', 'low', 'close', 'volume', 'amount'],
['000001.SZ'],
period='1d',
count=10
)
3. 解决方案与应对策略
既然我们无法直接修改底层 C++ 编译好的动态链接库,我们需要在 Python 业务层进行显式的类型转换和异常处理。
方法一:强制转换为 64 位浮点数或整数
在获取到 DataFrame 后,立即对可能溢出的列(如 volume, amount)进行类型转换。虽然如果底层传过来已经是负数,直接转 int64 无法恢复真实值,但如果是无符号 32 位溢出到有符号 32 位,可以通过位运算恢复。
import pandas as pd
import numpy as np
def fix_overflow(df, columns=['volume', 'amount']):
"""
尝试修复 32 位有符号整数溢出问题
如果数据本应是无符号 32 位(最大 42.9 亿),但被解析为有符号 32 位导致负数
"""
for col in columns:
if col in df.columns:
# 将负数转换回无符号 32 位的真实值,并存储为 64 位整数
df[col] = np.where(df[col] < 0, df[col] + 2**32, df[col]).astype(np.int64)
return df
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
data = ContextInfo.get_market_data_ex(
['volume', 'amount'], ['000001.SZ'], period='1d', count=5
)
df = data.get('000001.SZ')
if df is not None and not df.empty:
# 处理溢出
df_fixed = fix_overflow(df)
print(df_fixed)
方法二:使用财务数据或扩展数据交叉验证
如果单日成交量超过了无符号 32 位的上限(约 42.9 亿),上述修复方法也会失效。此时建议:
- 关注官方更新:向迅投/国信 QMT 官方反馈该 Bug,促使他们在底层将
volume和amount的数据类型升级为int64或double。 - 降频聚合:如果日线数据溢出,可以尝试拉取
1h或30m的分钟线数据(单根 Bar 成交量较小,不易溢出),然后在 Pandas 中自行resample聚合为日线数据。
总结
您对 xtdata.get_market_data_ex() 源码的追踪非常准确。在量化投研中,处理这种底层数据类型的边界问题(Edge Cases)是保证策略稳健性的关键一步。在官方修复底层 buffer 类型之前,建议在策略的数据清洗模块中加入针对大市值/高换手股票的溢出检测逻辑。