问题描述
解决方案
为什么 QMT 策略回测与实盘结果不一致?
在量化交易中,策略回测收益非常漂亮,但实盘交易却表现平平甚至亏损,这种现象被称为“回测与实盘的偏差”。在 QMT 平台上,造成这种差异的原因通常可以归纳为运行机制差异、未来函数的使用、交易成本与滑点以及市场环境变化四个方面。
以下是详细的深度解析:
1. QMT 平台的运行机制差异
QMT 的回测模式(Backtest)和实盘/模拟运行模式(Run/Live)在底层驱动机制上存在本质区别,这是导致结果不一致的最常见技术原因。
1.1 handlebar 函数的调用频率
- 回测模式:通常是逐 K 线运行。例如在日线回测中,
handlebar每天只被调用一次(通常被视为收盘后或收盘前的一瞬间)。 - 实盘模式:是行情驱动(Tick 驱动)。只要有新的分笔数据(Tick)推送,
handlebar就会被调用一次。- 风险点:如果策略逻辑没有严格限制在 K 线走完时才下单(例如未使用
ContextInfo.is_last_bar()判断),实盘中可能会在盘中因为价格波动多次触发信号,导致频繁开平仓,而回测中无法体现这种盘中波动。
- 风险点:如果策略逻辑没有严格限制在 K 线走完时才下单(例如未使用
1.2 信号触发与成交时机
- 回测模式:默认逻辑通常是“当前 K 线生成信号,下一根 K 线开盘成交”。
- 实盘模式:
- 如果使用
passorder且quickTrade=0:逻辑与回测一致,当前 K 线走完确认信号,下根 K 线第一笔 Tick 发单。 - 如果使用
passorder且quickTrade=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 的回测结果更具参考价值,建议采取以下措施:
- 严格对齐代码逻辑:
- 在
handlebar中使用if not ContextInfo.is_last_bar(): return来确保逻辑只在 K 线结束或最新 Tick 到来时运行。 - 明确
passorder中的quickTrade参数设置,理解其对成交时机的影响。
- 在
- 避免未来函数:
- 检查代码,确保决策只依赖于当前时间点之前已发生的数据。
- 增加回测严苛度:
- 在 QMT 回测参数中,设置较高的滑点(如 0.2%)和手续费(如万分之三以上)。
- 设置最大成交比例,避免在流动性枯竭时还能全额成交。
- 使用模拟盘验证:
- 在投入真金白银之前,先在 QMT 的“模拟交易”模式下运行一段时间,对比模拟盘记录与回测记录的每一笔交易,找出差异点。
Q&A
Q: QMT 中 quickTrade=1 和 quickTrade=0 在回测中有什么区别?
A: 在回测模式下,quickTrade 参数通常被忽略或行为不同于实盘。回测主要依据 K 线数据撮合。但在实盘中,quickTrade=1 会在信号触发的瞬间立即下单,而 0 会等到当前 K 线走完、下一根 K 线开始时下单。这是造成差异的主要原因之一。
Q: 为什么我的策略在回测中买入了涨停板的股票,实盘却买不进?
A: 回测系统通常只判断价格是否触及,默认能成交。但实盘中涨停板通常封单巨大,买单排队无法成交。需要在策略中加入逻辑:判断当前价格是否等于涨停价,如果是,则禁止买入。
Q: 如何在 QMT 代码中判断是否是实盘模式?
A: 可以使用 ContextInfo.do_back_test 属性。如果 ContextInfo.do_back_test 为 True,则表示当前处于回测模式;为 False 则表示处于实盘或模拟运行模式。利用此标志可以编写针对不同环境的兼容代码。