🚀 AI 一键生成 qmt 策略代码

为什么我的 QMT 策略回测很完美,实盘却亏钱?

qmt | 发布时间: 2025/12/18
以下内容由 EasyQuant 生成。

问题描述

为什么策略回测和实盘结果不一样?

解决方案

为什么 QMT 策略回测与实盘结果不一致?

在量化交易中,策略回测收益非常漂亮,但实盘交易却表现平平甚至亏损,这种现象被称为“回测与实盘的偏差”。在 QMT 平台上,造成这种差异的原因通常可以归纳为运行机制差异未来函数的使用交易成本与滑点以及市场环境变化四个方面。

以下是详细的深度解析:

1. QMT 平台的运行机制差异

QMT 的回测模式(Backtest)和实盘/模拟运行模式(Run/Live)在底层驱动机制上存在本质区别,这是导致结果不一致的最常见技术原因。

1.1 handlebar 函数的调用频率

  • 回测模式:通常是逐 K 线运行。例如在日线回测中,handlebar 每天只被调用一次(通常被视为收盘后或收盘前的一瞬间)。
  • 实盘模式:是行情驱动(Tick 驱动)。只要有新的分笔数据(Tick)推送,handlebar 就会被调用一次。
    • 风险点:如果策略逻辑没有严格限制在 K 线走完时才下单(例如未使用 ContextInfo.is_last_bar() 判断),实盘中可能会在盘中因为价格波动多次触发信号,导致频繁开平仓,而回测中无法体现这种盘中波动。

1.2 信号触发与成交时机

  • 回测模式:默认逻辑通常是“当前 K 线生成信号,下一根 K 线开盘成交”。
  • 实盘模式
    • 如果使用 passorderquickTrade=0:逻辑与回测一致,当前 K 线走完确认信号,下根 K 线第一笔 Tick 发单。
    • 如果使用 passorderquickTrade=1(立即下单):信号一旦成立立即发出委托。这会导致回测(通常基于收盘价结算)与实盘(基于盘中瞬时价格)产生巨大差异。
    • Do_order 函数:如果在回测中使用了 do_order,它会强制在信号产生的 K 线结束时立即成交(模拟收盘价成交),但在实盘中,如果计算耗时或网络延迟,成交价可能偏离收盘价。

2. “未来函数” (Look-ahead Bias)

这是回测中最致命的错误,指在策略中使用了当时时间点无法获取的数据

  • 典型案例:在日线策略中,使用当天的 close(收盘价)来决定是否在当天买入。
    • 回测表现:回测引擎已经知道全天的最高、最低和收盘价,因此策略会“预知”当天是涨是跌,从而在低位买入高位卖出。
    • 实盘表现:盘中运行时,当天的 close 价格是不断跳动的最新价,直到收盘那一刻才确定。实盘中无法以确定的收盘价在盘中成交。
  • QMT 中的防范:确保使用 ContextInfo.get_market_data_ex 获取历史数据时,不要包含当前正在运行且未结束的 K 线数据用于决策,或者明确逻辑是在 K 线结束时刻才进行判断。

3. 交易成本、滑点与流动性

回测环境通常是理想化的,而实盘环境是充满摩擦的。

3.1 滑点 (Slippage)

  • 回测:通常假设以“开盘价”或“收盘价”精准成交。
  • 实盘
    • 买入时:实际成交价可能高于看到的报价(因为需要吃掉卖一、卖二的单子)。
    • 卖出时:实际成交价可能低于看到的报价。
    • 冲击成本:如果资金量大,你的买单会直接推高股价,导致成本上升。回测很难完美模拟这种冲击。

3.2 手续费与税费

  • 回测:如果在 QMT 回测参数中未设置或设置过低的手续费(如忽略了印花税、过户费或最低佣金限制),回测收益会虚高。
  • 实盘:每一笔交易都有实打实的成本,高频策略受此影响尤为严重。

3.3 成交量限制 (Liquidity)

  • 回测:假设只要有价格就能成交任意数量。
  • 实盘:对于流动性差的股票(如跌停板、小盘股),可能根本买不到或卖不掉。QMT 回测虽然可以设置“最大成交比例”,但依然难以完全模拟真实的盘口深度。

4. 数据质量与处理差异

  • 复权问题:回测通常使用前复权数据,而实盘交易使用的是不复权的实时价格。如果在代码中没有正确处理复权因子(ContextInfo.get_divid_factors),会导致价格信号计算错误。
  • 停牌处理:回测数据中,停牌期间的数据处理方式(填充或跳过)可能与实盘中遇到停牌的处理逻辑不一致。

如何缩小回测与实盘的差距?

为了让 QMT 的回测结果更具参考价值,建议采取以下措施:

  1. 严格对齐代码逻辑
    • handlebar 中使用 if not ContextInfo.is_last_bar(): return 来确保逻辑只在 K 线结束或最新 Tick 到来时运行。
    • 明确 passorder 中的 quickTrade 参数设置,理解其对成交时机的影响。
  2. 避免未来函数
    • 检查代码,确保决策只依赖于当前时间点之前已发生的数据。
  3. 增加回测严苛度
    • 在 QMT 回测参数中,设置较高的滑点(如 0.2%)和手续费(如万分之三以上)。
    • 设置最大成交比例,避免在流动性枯竭时还能全额成交。
  4. 使用模拟盘验证
    • 在投入真金白银之前,先在 QMT 的“模拟交易”模式下运行一段时间,对比模拟盘记录与回测记录的每一笔交易,找出差异点。

Q&A

Q: QMT 中 quickTrade=1quickTrade=0 在回测中有什么区别?
A: 在回测模式下,quickTrade 参数通常被忽略或行为不同于实盘。回测主要依据 K 线数据撮合。但在实盘中,quickTrade=1 会在信号触发的瞬间立即下单,而 0 会等到当前 K 线走完、下一根 K 线开始时下单。这是造成差异的主要原因之一。

Q: 为什么我的策略在回测中买入了涨停板的股票,实盘却买不进?
A: 回测系统通常只判断价格是否触及,默认能成交。但实盘中涨停板通常封单巨大,买单排队无法成交。需要在策略中加入逻辑:判断当前价格是否等于涨停价,如果是,则禁止买入。

Q: 如何在 QMT 代码中判断是否是实盘模式?
A: 可以使用 ContextInfo.do_back_test 属性。如果 ContextInfo.do_back_testTrue,则表示当前处于回测模式;为 False 则表示处于实盘或模拟运行模式。利用此标志可以编写针对不同环境的兼容代码。