🚀 AI 一键生成 qmt 策略代码

如何避免在 QMT 策略开发中陷入“数据挖掘窥探”的陷阱?

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

问题描述

我测试了很多策略,选了回测效果最好的一个,但实盘还是不行,这是为什么?怎么避免这个问题?

解决方案

这是一个非常经典且普遍的量化交易问题。你遇到的情况通常被称为**“过拟合”(Overfitting)“幸存者偏差”**。在量化领域,回测表现极其优秀但实盘亏损的现象非常常见。

以下是导致这种现象的核心原因分析,以及在 QMT 平台中避免这些问题的具体建议。

核心原因分析:为什么回测好但实盘差?

1. 过度拟合 (Overfitting)

这是最常见的原因。当你测试了大量策略或参数组合,并从中选出“效果最好”的一个时,你实际上是在筛选运气,而不是筛选逻辑。

  • 现象:策略逻辑专门为了迎合过去某段特定的历史行情而设计。
  • 本质:策略记住了历史数据的“噪声”,而不是市场的“规律”。
  • 比喻:就像你为了通过一场已知的考试,背下了所有答案,但并没有学会解题方法。一旦题目(市场)稍微变化,你就无法应对。

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

在编写策略代码时,无意中使用了当时那个时间点无法获取的数据。

  • 常见错误
    • 在日线回测中,使用当天的收盘价(Close)去决定当天的开盘买入。
    • 使用了财务数据,但未考虑财报的公告日期(Announce Date),而是使用了报告期(Report Date)。例如,在1月1日使用了直到4月才发布的年报数据进行交易。
  • QMT 特性:在 QMT 中,如果使用 ContextInfo.get_financial_data 时未正确设置 report_type 或时间参数,容易引入此类偏差。

3. 交易成本与滑点低估

回测环境通常过于理想化,忽略了实际交易中的摩擦成本。

  • 滑点:回测通常假设你能以当前 K 线的收盘价或开盘价完美成交。但在实盘中,买入价可能比你看到的更高,卖出价更低,尤其是在资金量大或流动性差的标的上。
  • 手续费:忽略了印花税、佣金或最低收费标准。
  • 冲击成本:你的买卖行为本身会推高或拉低股价,回测无法体现这一点。

4. 市场风格切换 (Regime Change)

  • 现象:你的策略可能在过去两年的牛市或震荡市中表现很好,但实盘时市场进入了单边下跌或极度低波动环境。
  • 本质:策略缺乏普适性,只适应特定市场风格。

解决方案与建议:如何在 QMT 中避免?

1. 样本外测试 (Out-of-Sample Testing)

不要用所有历史数据来优化参数。

  • 做法:将数据分为“训练集”和“测试集”。例如,用 2018-2021 年的数据来研发策略和调整参数,然后用 2022-2023 年的数据进行验证。
  • 原则:如果在测试集(从未见过的数据)上表现大幅下降,说明策略过拟合,必须放弃。

2. 参数敏感性分析 (Parameter Sensitivity)

检查策略对参数变化的敏感程度。

  • 做法:如果你的策略在 MA(20) 时表现极好,但在 MA(19)MA(21) 时表现极差,说明该策略是“孤岛”策略,不可靠。
  • 目标:优秀的策略应该在参数小幅波动时,绩效保持相对稳定(“参数高原”)。

3. 在 QMT 中设置真实的费率与滑点

在 QMT 的策略编辑器或代码中,必须设置严格的费率和滑点。

  • 代码设置示例
    def init(ContextInfo):
        # 设置滑点:例如设置为 0.02 元,或者按比例设置
        # 1:固定值,2:比例
        ContextInfo.set_slippage(1, 0.02) 
        
        # 设置佣金:例如万分之三,最低5元
        # [买入印花税, 卖出印花税, 开仓佣金, 平仓佣金, 平今佣金, 最低佣金]
        # 注意:股票卖出有印花税(目前0.05%),买入无
        ContextInfo.set_commission(0, [0, 0.0005, 0.0003, 0.0003, 0, 5])
    

4. 严格检查未来函数

  • 财务数据:在 QMT 中获取财务数据时,务必注意 report_type 参数。
    • 错误做法:默认或不注意时间点。
    • 正确做法:确保使用 announce_time(公告日)作为基准,或者在回测逻辑中加入时间滞后。
  • 信号触发
    • 如果是日线策略,尽量使用 ContextInfo.do_order() 在次日开盘成交,或者在代码逻辑中确保使用的是上一根 K 线的数据来计算信号,并在当前 K 线(或次日)执行。
    • 使用 ContextInfo.get_market_data_ex 获取历史数据时,注意 end_time 不要包含未来数据。

5. 模拟盘验证 (Paper Trading)

在实盘投入资金前,先在 QMT 的模拟交易模式下运行一段时间(例如 1-3 个月)。

  • 目的:观察实盘信号是否与回测逻辑一致,检查是否有未预料到的撤单、废单或数据延迟问题。

6. 逻辑简化 (Occam's Razor)

  • 原则:越复杂的策略(规则越多、参数越多),过拟合的概率越大。
  • 建议:尽量减少过滤条件。一个依靠 10 个条件才盈利的策略,远不如一个依靠 2 个核心逻辑盈利的策略稳健。

Q&A

Q: 如何判断我的策略是否使用了未来函数?
A: 最简单的方法是查看成交明细。如果你的策略在日线回测中,能够以当天的最低价买入,或者在最高价卖出,或者在涨停板打开前买入,这通常意味着使用了未来函数。另外,检查代码中是否使用了类似 close (当根K线收盘价) 来决定当根K线的交易。

Q: QMT回测时,撮合机制是怎样的?
A: QMT 回测通常基于 K 线撮合。如果是日线回测,默认是在信号触发的次日开盘撮合(除非使用了 quickTrade 参数)。如果设置为收盘价成交,而你的信号依赖于收盘价,这就构成了逻辑闭环的未来函数。建议设置为“次日开盘价”撮合,这更符合实际。

Q: 为什么我的策略在小市值股票上回测很好,实盘买不进去?
A: 这是流动性陷阱。回测假设你可以无限量成交。对于小市值或低流动性股票,实盘中你的买单可能会推高价格(冲击成本),或者根本没有足够的卖单(流动性枯竭)。建议在策略中加入流动性过滤,例如剔除日成交额小于 2000 万的股票。

Q: 什么是“参数高原”?
A: 参数高原是指在参数的一定范围内,策略的绩效表现都比较好且稳定。例如,均线策略在 18日、19日、20日、21日、22日均线都能盈利,这就形成了一个高原。如果只有 20日盈利,其他都亏损,那就是“参数尖峰”,这种策略实盘极易失效。