QMT 有信号但没有下单怎么办?

策略日志显示了交易信号,但没有产生委托记录,需要依次检查运行模式、passorder 执行、quickTrade 参数设置和账户连接状态。最常见的原因是运行模式为模拟信号(不产生真实委托)、quickTrade 参数与执行上下文不匹配、或账户未正确连接。

问题现象

策略日志中出现了买入或卖出信号(如 print('买入信号') 正常输出),但在委托记录中看不到任何委托,账户持仓也没有变化。

直接判断

有信号但无委托,说明信号生成逻辑正常,但下单环节存在问题。需要从运行模式、passorder 调用、quickTrade 参数、账户状态四个维度排查。

信号生成和委托发送是两个独立的环节。信号生成只涉及策略逻辑计算,而委托发送需要满足多个条件:运行模式允许下单、passorder 被实际调用、quickTrade 参数匹配执行上下文、账户连接正常且权限充足。任何一个环节出问题都会导致有信号但无委托。

小建议 按以下 6 个步骤依次排查。每一步排查后确认该环节是否正常,再进入下一步。重点检查步骤 1(运行模式)和步骤 3(quickTrade 参数),这两个是最常见的原因。

排查步骤

步骤 1:确认运行模式(模拟信号不会真实下单)

QMT 的模拟信号模式只产生模拟信号,不会发送真实委托。如果策略运行在模拟信号模式下,即使 passorder 被调用也不会产生真实委托。

模拟信号模式的设计目的就是在不产生真实交易的情况下验证策略的信号生成逻辑。在该模式下,passorder 的调用会被系统拦截,不会发送到交易服务器。这是最容易被忽略的原因——开发者以为策略在"实盘"运行,实际上处于模拟信号模式。

操作方法: 在 QMT 客户端中确认策略的运行模式。查看运行模式是否为"仿真交易"或"真实交易"。如果是"模拟信号",切换为"仿真交易"或"真实交易"模式。

正确结果: 运行模式显示为仿真交易环境或真实交易环境,passorder 调用后产生委托记录。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 检查当前运行模式
    if ContextInfo.do_back_test:
        print('当前为回测环境')
    else:
        print('当前为实时环境')
    close = ContextInfo.get_close_price()
    if close > 10:
        print(f'买入信号触发,价格: {close}')
        # 下单
        ContextInfo.passorder(
            0, 1101, ContextInfo.accountid,
            '000001.SZ', 11, close, 100,
            1, ContextInfo
        )
        print('passorder 已调用')

步骤 2:确认 passorder 是否真正被执行

passorder 调用可能因为条件分支未进入、异常被捕获或代码逻辑错误而未实际执行。

有时信号 print 语句在条件判断内,但 passorder 调用在另一个条件分支中,或者 passorder 被包裹在 try-except 中而异常被静默处理。需要在 passorder 调用前后都添加日志确认其被执行。

操作方法: 在 passorder 调用前添加 print('准备下单'),在 passorder 调用后添加 print('下单完成')。运行策略后检查两条日志是否都出现。如果只有"准备下单"没有"下单完成",说明 passorder 抛出了异常。

正确结果: passorder 调用前后的日志都正常输出,无异常信息。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    close = ContextInfo.get_close_price()
    if close > 10:
        print(f'买入信号触发,价格: {close}')
        print('准备调用 passorder...')
        try:
            ContextInfo.passorder(
                0, 1101, ContextInfo.accountid,
                '000001.SZ', 11, close, 100,
                1, ContextInfo
            )
            print('passorder 调用完成')
        except Exception as e:
            print(f'passorder 调用异常: {e}')

步骤 3:检查 quickTrade 参数

quickTrade 参数必须与 passorder 的调用上下文匹配。handlebar 中使用 quickTrade=1,subscribe 回调和 run_time 回调中使用 quickTrade=2。quickTrade=0 会弹出确认弹窗。

最常见的错误是在 subscribe 回调或 run_time 回调中使用 quickTrade=1,此时委托无法发送。另一个常见错误是在自动化策略中使用 quickTrade=0,弹窗阻塞了策略执行但无人确认,导致委托卡在确认环节。

操作方法: 检查 passorder 调用中的 quickTrade 参数值。确认调用位置:如果在 handlebar 中,quickTrade 应为 1;如果在 subscribe 回调或 run_time 回调中,quickTrade 应为 2。确保自动化策略不使用 quickTrade=0。

正确结果: quickTrade 参数与调用上下文匹配,委托正常发送无需手动确认。

# handlebar 中:quickTrade=1
def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    close = ContextInfo.get_close_price()
    if close > 10:
        ContextInfo.passorder(
            0, 1101, ContextInfo.accountid,
            '000001.SZ', 11, close, 100,
            1,  # handlebar 中用 1
            ContextInfo
        )

# subscribe 回调中:quickTrade=2
def on_quote(ContextInfo, accountid, tick):
    if tick.m_dLastPrice > 15:
        ContextInfo.passorder(
            0, 1101, accountid,
            '000001.SZ', 12, tick.m_dLastPrice, 100,
            2,  # 回调中用 2
            ContextInfo
        )

步骤 4:检查账户状态和连接

passorder 需要有效的账户连接才能发送委托。如果账户未登录、连接断开或交易权限未开通,委托无法发送。

账户状态问题可能不会在策略日志中直接提示。QMT 客户端与交易服务器的连接可能因为网络问题、会话超时或维护时段而断开。此外,如果账户未开通交易权限或资金不足,委托也会被拒绝。

操作方法: 在 QMT 客户端中检查账户连接状态,确认显示为"已连接"。确认交易权限已开通(联系客户端提供方确认)。在 handlebar 中添加账户状态检查代码。

正确结果: 账户连接正常,交易权限已开通,可用资金充足。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 检查账户状态
    account = ContextInfo.get_account()
    if account:
        print(f'账户状态 - 可用资金: {account.m_dAvailable}')
    else:
        print('警告:无法获取账户信息,可能未连接')
        return
    
    close = ContextInfo.get_close_price()
    if close > 10:
        # 检查资金是否充足
        cost = 100 * close
        if account.m_dAvailable >= cost:
            ContextInfo.passorder(
                0, 1101, ContextInfo.accountid,
                '000001.SZ', 11, close, 100,
                1, ContextInfo
            )
            print(f'下单成功,价格: {close}')
        else:
            print(f'资金不足: 需要 {cost}, 可用 {account.m_dAvailable}')

步骤 5:检查价格和数量参数

passorder 的价格和数量参数必须有效。价格不能为零或负数(市价单除外),数量必须是正确的交易单位(如股票为 100 股的整数倍)。

常见问题包括:限价单价格为 0、数量为 0 或非 100 的整数倍、品种代码格式错误、价格类型不匹配。这些问题可能导致委托被交易服务器拒绝,但策略日志中不一定有明确提示。

操作方法: 在 passorder 调用前打印所有参数值,检查价格是否合理、数量是否正确、品种代码格式是否正确。参考下方常见原因汇总表核对参数。

正确结果: 所有参数值合理有效,委托被交易服务器接受。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    close = ContextInfo.get_close_price()
    stock = '000001.SZ'
    volume = 100
    price = close
    price_type = 11  # 限价单
    
    # 参数检查
    print(f'下单参数: 品种={stock}, 数量={volume}, 价格={price}, 价格类型={price_type}')
    
    # 验证参数有效性
    if price <= 0:
        print('错误:价格无效')
        return
    if volume <= 0 or volume % 100 != 0:
        print('错误:数量无效,须为100的整数倍')
        return
    
    ContextInfo.passorder(
        0, 1101, ContextInfo.accountid,
        stock, price_type, price, volume,
        1, ContextInfo
    )
    print('委托已发送')

步骤 6:检查委托和错误回报

如果 passorder 被调用但委托未出现在委托列表中,可能是委托被交易服务器拒绝。通过 order_callback 回调可以获取委托的拒绝原因。

交易服务器拒绝委托时,会返回错误信息。如果没有定义 order_callback 回调,错误信息可能不会显示在策略日志中。通过在 QMT 客户端的委托管理界面查看,或定义 order_callback 捕获错误信息,可以定位拒绝原因。

操作方法: 在策略中定义 order_callback 函数捕获委托回报。同时在 QMT 客户端的"委托"或"撤单"页面查看是否有被拒绝的委托记录。

正确结果: 委托出现在委托列表中,order_callback 显示委托状态正常。如果被拒绝,错误信息明确显示原因。

def order_callback(ContextInfo, order_info):
    """委托回报回调"""
    print(f'委托回报: {order_info.m_strStockCode}')
    print(f'  状态码: {order_info.m_nOrderStatus}')
    if hasattr(order_info, 'm_strErrorInfo') and order_info.m_strErrorInfo:
        print(f'  错误信息: {order_info.m_strErrorInfo}')
    print(f'  委托号: {order_info.m_strOrderSysID}')

最小验证代码

使用以下最小代码验证下单流程是否畅通。此代码在满足条件时发送一笔最小委托:

def init(ContextInfo):
    ContextInfo.set_universe(['000001.SZ'])
    ContextInfo.order_tested = False
    print('策略初始化完成')

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    if ContextInfo.order_tested:
        return
    
    close = ContextInfo.get_close_price()
    if close and close > 0:
        print(f'当前价格: {close},准备发送测试委托')
        ContextInfo.passorder(
            0, 1101, ContextInfo.accountid,
            '000001.SZ', 11, close, 100,
            1, ContextInfo
        )
        ContextInfo.order_tested = True
        print('测试委托已发送')

def order_callback(ContextInfo, order_info):
    print(f'收到委托回报,状态: {order_info.m_nOrderStatus}')
    if hasattr(order_info, 'm_strErrorInfo') and order_info.m_strErrorInfo:
        print(f'错误: {order_info.m_strErrorInfo}')

常见原因汇总表

有信号但无委托的常见原因汇总如下。

原因 现象 解决方法
运行在模拟信号模式 日志有信号,无委托记录 切换为仿真交易或真实交易环境
quickTrade 与上下文不匹配 passorder 被调用但无委托 handlebar 中用 1,回调中用 2
quickTrade=0 阻塞 弹出确认窗口无人点击 自动化策略改用 1 或 2
账户未连接 无法获取账户信息 检查客户端登录和连接状态
交易权限未开通 委托被拒绝 联系客户端提供方开通权限
资金不足 委托被拒绝 检查可用资金,减少委托数量
品种代码错误 委托被拒绝 确认品种代码格式(如 000001.SZ)
价格或数量无效 委托被拒绝 价格大于 0,数量为 100 的整数倍
passorder 未实际执行 信号日志有但 passorder 前后无日志 检查条件分支和异常处理
委托被交易规则拒绝 order_callback 显示错误 检查涨跌停限制、最小交易单位等

小建议 将此表作为排查清单,按从上到下的顺序逐一排除。在策略中添加完善的日志和 order_callback 回调,可以在问题发生时快速定位原因。涉及真实交易的策略,务必先在仿真交易环境中验证完整的下单流程。