🚀 AI 一键生成 qmt 策略代码

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

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

问题描述

我的策略回测曲线很好看,但为什么一跑实盘效果就差很多?有哪些可能的原因?

解决方案

这是一个非常经典且普遍的量化交易问题。策略回测表现优异(高收益、低回撤),但实盘交易却大相径庭,这种现象通常被称为**“回测失真”**。

造成这种情况的原因主要可以归纳为三大类:代码逻辑错误(未来函数)、模型过度拟合、以及环境与交易摩擦的差异

以下是基于量化交易原理及 QMT 平台特性的详细分析:

1. 未来函数(Look-ahead Bias)

这是最常见导致回测虚假繁荣的原因。策略在计算信号时,使用了当时实际上无法获取的数据。

  • 收盘价陷阱:在日线级别回测时,如果在当天的逻辑中使用了当天的 close(收盘价)来决定是否在当天买入,这就是未来函数。因为在盘中交易时,收盘价还没有产生。
  • 最高/最低价陷阱:如果在盘中使用了当天的 highlow 来触发止盈止损,回测引擎通常会假设你能以该价格成交,但实盘中你可能根本无法在该价格触达的瞬间成交。
  • 财务数据引用:在回测中直接引用了财报发布当季的数据,但未考虑财报实际的公告日期(Announce Date)。例如,一季报数据可能在4月底才公布,如果在4月1日就用该数据进行选股,就是使用了未来数据。

2. 信号闪烁(Repainting)与 QMT 运行机制

QMT 的 handlebar 机制在实盘和回测中略有不同,处理不当会导致信号消失。

  • 盘中信号消失:在实盘或模拟盘中,handlebar 会随每一个 Tick(分笔数据)驱动运行。如果你的策略逻辑是“股价 > 均线则买入”,盘中某一瞬间股价冲高触发买入,但收盘时股价回落,信号消失。回测通常是基于已完成的 K 线(Bar)计算的,不会记录这种盘中的“虚假信号”,而实盘中你可能已经成交了,导致实盘亏损但回测中没有这笔交易。
  • 解决方案
    • 使用 ContextInfo.is_last_bar() 判定是否为 K 线结束时刻。
    • 或者逻辑改为:当前 K 线计算信号,下一根 K 线开盘(Open) 执行交易。
    • 检查 passorder 函数中的 quickTrade 参数设置。如果设置为 1(立即下单),容易出现信号闪烁问题;设置为 0 通常更稳健(等待 K 线走完)。

3. 过度拟合(Overfitting)

策略是为了迎合历史数据而“硬凑”出来的。

  • 参数孤岛:例如,你发现 MA(23)RSI(14) 在过去两年效果极好,但换成 MA(24) 效果就崩塌。这说明策略没有逻辑支撑,只是恰好撞上了历史数据的噪声。
  • 规律失效:市场风格是轮动的。回测曲线好看可能只是因为策略刚好适应了过去某一段特定的行情(如单边牛市或小盘股行情),一旦实盘遇到市场风格切换,策略就会失效。

4. 交易摩擦与滑点(Frictions & Slippage)

回测环境过于理想化,忽略了真实交易的成本。

  • 滑点设置不足:回测通常假设以“收盘价”或“开盘价”精准成交。实盘中,由于行情波动和网络延迟,实际成交价往往比信号价差。如果策略是高频或短线策略,几个跳价(Tick)的滑点就可能吃掉所有利润。
  • 手续费低估:未在回测中扣除印花税(特别是卖出时的千分之0.5)、佣金(万分之几)以及期货的平今仓手续费。
  • 流动性陷阱:回测中,你可以在跌停板买入或涨停板卖出,或者在成交量极小的股票上买入几百万金额。实盘中,这些操作根本无法成交,或者你的买入行为本身会推高股价(冲击成本),导致成本大幅上升。

5. 幸存者偏差(Survivorship Bias)

  • 数据源问题:如果你的股票池只包含当前上市的股票,那么你实际上剔除了历史上那些因为业绩差而退市或暴雷的股票。回测时你“天然”避开了这些雷,导致收益虚高。实盘中,你买入的股票未来是有可能退市的。

6. QMT 平台特有的数据差异

  • 复权方式:回测时通常使用前复权数据,而实盘交易使用的是不复权价格。如果在计算指标(如均线)时混淆了复权与不复权数据,会导致信号错位。
  • 数据质量:回测使用的历史数据通常经过清洗,而实盘接收的是实时推送数据,可能会偶发丢包或异常值(如价格瞬间归零又恢复),如果代码没有异常值过滤逻辑,会触发错误交易。

建议排查步骤

  1. 检查信号时机:将策略改为“收盘计算信号,次日开盘成交”,看回测效果是否大幅下降。如果下降,说明原策略依赖盘中运气或未来函数。
  2. 增加压力测试:在 QMT 回测参数中,将滑点(Slippage)设置为 2‰ 或更高,将手续费设置为真实费率的 1.5 倍。如果策略这就失效了,说明逻辑太脆弱。
  3. 检查 QMT 代码
    • 确保使用了 ContextInfo.get_market_data_ex 等规范接口。
    • 确认 passorderquickTrade 参数是否符合你的预期。
    • 确认是否正确处理了 ContextInfo.is_new_bar()ContextInfo.is_last_bar()
  4. 样本外测试:将历史数据分为两段(例如 2018-2021 训练,2022-2023 验证)。如果在验证集上表现很差,说明过度拟合。

Q&A

Q: 什么是 QMT 中的“未来函数”?
A: 在 QMT 中,如果在计算当前 K 线(Bar)的指标时,引用了该 K 线尚未走完时的数据(如在盘中就确信当天的收盘价是 X),或者引用了当时尚未发布的财务数据,即为未来函数。

Q: 如何在 QMT 回测中设置滑点以模拟真实情况?
A: 可以使用 ContextInfo.set_slippage(type, value) 接口。例如 ContextInfo.set_slippage(1, 0.02) 表示按固定值 0.02 元设置滑点,或者在策略编辑器的“回测参数”面板中直接设置。

Q: 为什么我的策略在 QMT 模拟盘中有信号,但没有委托?
A: 这通常不是回测失真,而是代码逻辑问题。请检查:1. 资金是否充足;2. 是否处于非交易时间;3. passorder 函数的参数是否正确(如账号ID、操作类型);4. 是否触发了风控限制。

Q: 如何避免信号闪烁?
A: 最稳健的方法是使用“走完 K 线”模式。在 Python 代码中,可以通过判断 ContextInfo.is_last_bar() 是否为 True 且当前时间接近收盘,或者在 handlebar 中计算信号,但使用 do_order 在下一根 K 线的起始时刻进行下单。