🚀 AI 一键生成 joinquant 策略代码
立即体验

聚宽模拟盘与本地回测收益差距大?教你三步排查行情、交易时点与未来函数

joinquant | 发布时间: 2026/8/11
以下内容由 EasyQuant 生成。

问题描述

聚宽模拟盘与本地回测收益差异:如何排查策略执行不一致?
同一套聚宽 ETF 策略在模拟盘和本地回测中的收益差距很大时,应该如何检查行情数据、交易时点和未来函数问题?

解决方案

在聚宽(JoinQuant)量化平台上,同一套策略在模拟盘和回测中出现显著的收益差异是量化交易者经常遇到的棘手问题。要彻底解决这一“不一致”痛点,需要从复权模式、交易撮合时点、未来函数、随机因素及系统机制四个维度进行系统化排查:

一、 核心排查:复权模式与真实价格(最常见原因)

1. 检查是否开启了动态复权(真实价格)模式

  • 回测默认机制:传统的“前复权”回测模式使用的是基于今天(或回测当天)的复权因子。这会导致回测中使用的历史价格与历史当时的真实价格不符,甚至引入未来函数。
  • 模拟盘默认机制:模拟盘在未开启动态复权时,使用的是基于模拟交易创建日期的后复权价格,这会导致模拟盘看到的价格与炒股软件或回测中看到的价格不一致。
  • 解决方案强烈建议在回测和模拟盘中均开启动态复权模式。在 initialize 函数中加入以下代码:
    def initialize(context):
        # 开启真实价格(动态复权)模式
        set_option('use_real_price', True)
    
  • 注意:开启真实价格后,不要跨日期缓存 historyattribute_history 等 API 返回的结果,因为不同日期看到的前复权价格可能不同。

二、 交易时点与撮合机制排查

1. 盘中运行时间偏差

  • 日级模拟盘提前运行:对于使用当日开盘价撮合的日级模拟盘,出于减少并发运行数量的目的,聚宽系统会提前在 09:27 ~ 09:30 之间运行策略,虽然 context.current_dt 仍显示为 09:30。这可能导致模拟盘下单时,某些标的(如股指期货)尚未产生集合竞价,从而因“成交量为0”被拒绝交易。
  • 定时任务执行顺序:在同一时间点,定时任务的运行顺序为 run_monthly > run_weekly > run_daily。请确保您的策略不依赖于这些计划任务的微观执行顺序。

2. 撮合规则差异

  • 盘口撮合(仅限模拟盘):模拟盘可以开启盘口撮合(set_option('match_with_order_book', True)),而回测通常使用 Bar(K线)撮合。如果模拟盘开启了盘口撮合,而回测没有,会导致成交价和成交量出现偏差。
  • 成交量比例限制:回测中可以通过 set_option('order_volume_ratio', value) 限制单笔订单成交量不超过当日总成交量的一定比例,而模拟盘在未启用盘口撮合时,市价单超出部分会撤单,限价单会挂单。请检查两者的成交量限制参数是否完全一致。

三、 未来函数与数据获取排查

1. 开启避免未来数据模式

  • 策略在回测中可能无意间获取了“未来”的数据(例如在开盘前获取了当天的收盘价、财务数据等),而模拟盘无法获取未来数据,导致回测收益虚高。
  • 解决方案:在 initialize 中开启未来数据检测:
    set_option("avoid_future_data", True)
    
    开启后,如果策略尝试获取未来数据,系统会抛出 FutureDataError 异常或自动剔除未来数据。

2. 财务数据获取时点

  • get_fundamentals 在盘后(15:00)才能获取当天交易日的数据。如果在盘中或开盘前调用并使用了当天的财务数据,就会引入未来函数。请确保传入的 date 参数不大于 context.current_dt 的前一天。

四、 模拟盘特有机制与随机性排查

1. 模拟盘系统延迟与状态保存

  • 10秒系统延迟:模拟盘有 10 秒的系统延迟,日志中的时间是逻辑时间而非实际物理时间。
  • 开盘成交量检查:模拟盘在下单时会检查开盘到下单时刻的累积成交量,若为 0(例如在 9:25 之前或刚开盘无成交时下单)会拒绝委托。回测中可能没有如此严格的限制。
  • 全局变量 g 的序列化限制:模拟盘每天收盘后会休眠并使用 pickle 保存 g 对象。如果 g 中保存了无法序列化的对象(如数据库连接 query、打开的文件、网络连接等),重启后这些变量会丢失或抛出异常。请将这些不可序列化的初始化操作放在 process_initialize(context) 中,并以双下划线 __ 开头命名(如 g.__q),以便系统在序列化时自动忽略。

2. 代码修改未在模拟盘生效

  • 模拟盘更换代码后,initialize 函数不会重新执行。如果修改了全局变量的初始值,必须在 after_code_changed(context) 函数中进行显式修改,否则模拟盘仍会沿用旧的代码逻辑和变量值。

3. 排除代码中的随机性

  • 检查策略中是否使用了未设定随机种子的随机数生成器(如 numpy.random)、对 dict 进行无序遍历、或使用了不稳定的排序算法,这些都会导致模拟盘与回测产生分歧。