QMT 实时行情不更新或为空怎么办?

订阅行情后回调不触发,或获取的数据为空,通常是数据权限未开通、订阅方式不正确或历史数据未下载导致。最常见的原因包括:行情权限未开通、品种代码格式错误、非交易时段、历史数据未下载到本地。按以下步骤逐一排查即可定位问题。

问题现象

策略中通过 subscribe_quote 订阅了行情但回调函数不被触发,或者通过 get_history_data、get_market_data 等函数获取数据时返回空值或 None。

直接判断

行情数据为空或不更新,通常有三个层面的原因:权限层面(行情权限未开通)、数据层面(历史数据未下载或品种代码错误)、环境层面(非交易时段或网络问题)。

QMT 的行情数据分为实时行情和历史行情。实时行情通过 subscribe_quote 订阅获取,需要行情权限和交易时段内才有数据推送。历史行情通过 get_history_data 等函数获取,需要事先下载到本地。两者的数据来源和获取条件不同,排查方法也不同。

小建议 按以下 6 个步骤从权限到代码逐一排查。先确认权限和环境等外部条件,再检查代码逻辑。

排查步骤

步骤 1:确认行情权限已开通

QMT 的行情数据需要相应的权限才能获取。Level-1 行情包括基本的开高低收量数据,Level-2 行情包括十档买卖盘和逐笔成交数据。如果权限未开通,相关数据接口将返回空值。

行情权限由客户端提供方控制。不同权限等级能获取的数据类型不同。如果策略需要 Level-2 数据(如十档买卖盘)但只开通了 Level-1 权限,相关接口会返回空值。历史数据下载也需要相应的权限。

操作方法: 联系客户端提供方确认行情权限状态。在 QMT 客户端中查看行情权限是否已开通。尝试获取最基本的 Level-1 数据(如收盘价)验证权限是否正常。

正确结果: 行情权限已开通,基本行情数据(开高低收量)可以正常获取。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 测试基本行情数据获取
    close = ContextInfo.get_close_price()
    volume = ContextInfo.get_volume()
    print(f'收盘价: {close}, 成交量: {volume}')
    if close is None or close <= 0:
        print('警告:无法获取基本行情数据,可能权限未开通')

步骤 2:检查订阅代码是否正确

subscribe_quote 的品种代码必须符合 QMT 的格式规范。A 股代码格式为"代码.市场",如 000001.SZ(深圳)、600000.SH(上海)。代码格式错误会导致订阅失败或回调不触发。

常见的代码格式错误包括:缺少市场后缀(如只写 000001)、市场后缀错误(如 000001.SH)、代码位数不对。QMT 对品种代码格式有严格要求,格式不匹配时不会报错但数据为空。

操作方法: 检查 subscribe_quote 和 get_history_data 中的品种代码格式。确认代码与市场后缀匹配。在 QMT 客户端的行情界面确认品种代码的正确格式。

正确结果: 品种代码格式正确,行情数据正常获取。

def init(ContextInfo):
    # 正确的品种代码格式
    stocks = ['000001.SZ', '600000.SH']  # 深圳.上海
    
    for stock in stocks:
        ContextInfo.subscribe_quote(stock, 0, 'on_quote', ContextInfo)
    
    # 错误的品种代码格式(不会报错但数据为空)
    # ContextInfo.subscribe_quote('000001', ...)     # 缺少市场后缀
    # ContextInfo.subscribe_quote('000001.SH', ...)  # 市场后缀错误
    
    print(f'已订阅品种: {stocks}')

def on_quote(ContextInfo, accountid, tick):
    if tick and tick.m_dLastPrice > 0:
        print(f'{tick.m_strCode} 最新价: {tick.m_dLastPrice}')
    else:
        print('收到回调但数据为空,检查品种代码')

步骤 3:检查网络连接

QMT 需要与行情服务器保持网络连接才能接收实时行情。网络断开会导致行情数据停止更新,subscribe 回调不再触发。

网络问题可能表现为:完全无法获取数据、数据更新延迟或间歇性中断。QMT 客户端在网络恢复后通常会自动重连,但重连期间的行情数据会丢失。如果使用 Wi-Fi 网络,不稳定的连接可能导致行情数据频繁中断。

操作方法: 在 QMT 客户端中检查行情连接状态。确认网络连接稳定。尝试切换网络(如有线网络)测试是否改善。查看 QMT 客户端是否有网络断开重连的提示。

正确结果: 网络连接稳定,行情数据持续更新,subscribe 回调按行情频率触发。

步骤 4:确认交易时间段

实时行情只在交易时间段内推送。A 股的交易时间为周一至周五 9:30-11:30 和 13:00-15:00。非交易时段没有实时行情推送,subscribe 回调不会触发。

在非交易时段(如盘前 9:00-9:25 的集合竞价准备阶段、午间休市、收盘后、周末和节假日),行情服务器不推送实时数据。此时 subscribe 回调不触发是正常现象,不是 bug。盘前集合竞价阶段(9:20-9:25)可能有部分行情数据。

操作方法: 确认当前时间是否在交易时段内。如果在非交易时段测试,切换到回测环境使用历史数据测试,或等待交易时段再验证实时行情。

正确结果: 在交易时段内,行情数据正常推送,subscribe 回调按行情频率触发。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    from datetime import datetime
    now = datetime.now()
    hour_min = now.hour * 100 + now.minute
    
    # 判断是否在交易时段
    is_trading = (930 <= hour_min <= 1130) or (1300 <= hour_min <= 1500)
    is_weekday = now.weekday() < 5  # 周一到周五
    
    if is_trading and is_weekday:
        last_price = ContextInfo.get_last_price('000001.SZ')
        print(f'交易时段,最新价: {last_price}')
    else:
        print(f'非交易时段 (时间: {now.strftime("%H:%M")} 星期{now.weekday()+1}),行情不更新是正常的')

步骤 5:检查历史数据是否已下载

get_history_data、get_market_data 等函数获取的历史数据需要事先下载到本地。如果历史数据未下载,这些函数会返回空值或数据不完整。

QMT 的历史数据存储在本地,需要通过 do_download_history_data 或客户端的手动下载功能获取。如果策略请求的历史数据范围超出了已下载的数据范围,超出部分的数据为空。数据下载是异步的,下载完成前获取数据可能不完整。

操作方法: 在 init 中调用 do_download_history_data 确保数据已下载。在 QMT 客户端的数据管理界面确认数据下载状态。获取数据后检查返回值的长度是否与请求的数量一致。

正确结果: 历史数据已下载,get_history_data 返回的数据长度与请求的数量一致。

def init(ContextInfo):
    stock = '000001.SZ'
    # 下载历史数据(确保数据可用)
    ContextInfo.do_download_history_data(stock, '1d', '20230101', '20261231')
    ContextInfo.do_download_history_data(stock, '1m', '20230101', '20261231')
    print(f'{stock} 历史数据下载完成')

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 获取历史数据并检查完整性
    closes = ContextInfo.get_history_data('close', 20, '000001.SZ')
    if closes:
        print(f'获取到 {len(closes)} 条收盘价数据')
        if len(closes) < 20:
            print(f'警告:请求 20 条但只获取到 {len(closes)} 条,数据可能不完整')
    else:
        print('警告:历史数据为空,请检查数据下载')

步骤 6:检查回调函数定义

subscribe_quote 的回调函数必须正确定义,函数名与 subscribe_quote 中注册的名称一致,函数签名必须接收三个参数:ContextInfo、accountid、tick。

回调函数名拼写错误、参数数量不匹配、函数未定义等问题都会导致回调不触发或触发后报错。subscribe_quote 的第三个参数是回调函数名的字符串形式,必须与实际定义的函数名完全一致。回调函数中的错误可能被静默处理,不会在输出面板显示。

操作方法: 检查回调函数名是否与 subscribe_quote 注册的名称一致。确认函数签名包含三个参数。在回调函数中添加 try-except 捕获可能的异常。

正确结果: 回调函数被正常触发,tick 数据不为空,无异常信息。

def init(ContextInfo):
    # 注册回调:函数名 'on_quote' 必须与下面的 def on_quote 一致
    ContextInfo.subscribe_quote('000001.SZ', 0, 'on_quote', ContextInfo)
    print('行情订阅已注册,回调函数名: on_quote')

def on_quote(ContextInfo, accountid, tick):
    # 确保函数名一致,参数为三个
    try:
        if tick is None:
            print('收到回调但 tick 为空')
            return
        print(f'回调触发: {tick.m_strCode} 价格={tick.m_dLastPrice}')
    except Exception as e:
        print(f'回调函数异常: {e}')

最小验证代码

使用以下代码验证行情数据获取是否正常。此代码同时测试实时行情和历史数据:

def init(ContextInfo):
    ContextInfo.set_universe(['000001.SZ'])
    # 下载历史数据
    ContextInfo.do_download_history_data('000001.SZ', '1d', '20230101', '20261231')
    # 订阅实时行情
    ContextInfo.subscribe_quote('000001.SZ', 0, 'on_quote', ContextInfo)
    ContextInfo.callback_count = 0
    print('初始化完成,已订阅行情')

def on_quote(ContextInfo, accountid, tick):
    ContextInfo.callback_count += 1
    if tick:
        print(f'[回调 #{ContextInfo.callback_count}] {tick.m_strCode} 最新价: {tick.m_dLastPrice}')
    else:
        print(f'[回调 #{ContextInfo.callback_count}] tick 为空')

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 测试历史数据
    closes = ContextInfo.get_history_data('close', 5, '000001.SZ')
    if closes:
        print(f'历史数据: {len(closes)} 条, 最新: {closes[-1]}')
    else:
        print('历史数据为空')
    # 测试实时数据
    last_price = ContextInfo.get_last_price('000001.SZ')
    print(f'实时最新价: {last_price}')
    # 回调计数
    print(f'subscribe 回调次数: {ContextInfo.callback_count}')

预期输出:

  • init 执行后显示"初始化完成"
  • handlebar 中历史数据获取到 5 条收盘价
  • handlebar 中实时最新价不为 None
  • 交易时段内 on_quote 回调按行情频率触发,回调次数持续增加

小建议 如果历史数据正常但实时行情为空,重点检查步骤 1(权限)和步骤 4(交易时段)。如果实时行情正常但历史数据为空,重点检查步骤 5(数据下载)。如果两者都为空,重点检查步骤 2(品种代码)和步骤 3(网络连接)。