🚀 AI 一键生成 joinquant 策略代码

JoinQuant 严谨回测:如何通过 avoid_future_data 选项自动检测未来函数?

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

问题描述

怎样才能在回测中自动检查和避免使用未来数据?

解决方案

在聚宽(JoinQuant)平台的回测中,自动检查和避免使用未来数据(Look-ahead Bias)最直接且有效的方法是使用策略设置函数 set_option 开启防未来数据模式。

以下是具体的实现方法和详细说明:

1. 核心解决方案:开启 avoid_future_data

在策略的初始化函数 initialize(context) 中,调用 set_option 函数并将 avoid_future_data 选项设置为 True

代码示例

# -*- coding: utf-8 -*-
def initialize(context):
    # 开启避免未来数据模式
    # 默认为 False,设置为 True 后,系统会自动检测并拦截未来数据的获取
    set_option('avoid_future_data', True)
    
    # 其他初始化代码...
    g.security = '000001.XSHE'
    set_benchmark('000300.XSHG')

2. 该选项的工作机制

开启该选项后,回测引擎会从以下两个层面进行严格检查:

  • 抛出异常(针对带时间参数的API):
    如果策略代码中使用了包含时间参数的 API(例如 get_price, get_bars 等),并且请求的时间范围超过了当前回测的时间点(context.current_dt),系统会直接抛出 FutureDataError 异常,终止回测。

    • 例子:在 2023-01-01 的回测时间点,尝试获取 2023-01-02 的收盘价。
  • 自动剔除(针对不带时间参数的API):
    对于用户无法通过参数主动规避未来数据的 API(例如 get_call_auction 等),系统会自动过滤掉返回结果中的未来数据,只返回当前时刻已知的数据,而不会抛出异常。

3. 常见的未来数据陷阱及规避建议

即使开启了上述选项,理解未来数据的来源对于编写健壮的策略依然至关重要。以下是常见的未来数据类型:

A. 价格类未来数据

  • 现象:在当天的开盘前(before_trading_start)或盘中使用了当天的收盘价(close)、最高价(high)或最低价(low)进行决策。
  • 规避
    • before_trading_start 中,只能使用前一个交易日及之前的数据。
    • 使用 historyattribute_history 时,注意 skip_paused 和数据长度,确保不包含当日未发生的数据。

B. 财务类未来数据

  • 现象:在财报统计截止日(如 12月31日)直接使用了该季度的财务数据,而实际上财报发布通常有延迟(如次年 3月才发布)。
  • 规避
    • 使用 get_fundamentals 查询财务数据时,不要直接指定 statDate(统计期),而是指定 date(查询日期)。
    • 系统会根据 date 返回该日期之前已经公告的最新财报数据,从而自动避免未来函数。

C. 停牌与成分股数据

  • 现象:在回测中交易了当时实际上已经停牌、未上市或已退市的股票,或者使用了当时还未纳入指数的成分股。
  • 规避
    • 使用 get_current_data()[security].paused 判断停牌状态。
    • 使用 get_index_stocks 获取指数成分股时,务必传入 date 参数(如 date=context.current_dt),以获取当时真实的成分股列表。

4. 局限性说明

虽然 set_option('avoid_future_data', True) 功能强大,但它无法检测以下情况:

  1. 外部数据:如果您通过自定义库或文件导入了包含未来信息的外部数据(如本地 CSV 文件),系统无法检测。
  2. 硬编码逻辑:如果在代码中直接写死了未来的日期或特定的高低点位(例如:if context.current_dt == datetime(2015, 6, 12): sell_all()),这种逻辑性未来函数无法被检测。

Q&A

Q1: 开启 avoid_future_data 会影响回测速度吗?
A1: 会有轻微的影响,因为系统需要在每次数据请求时增加额外的时间戳校验逻辑,但为了策略的严谨性,这种性能损耗通常是值得的。

Q2: 为什么我在 before_trading_start 获取当天的 open 价报错了?
A2: before_trading_start 运行在 09:00~09:30 之间(通常逻辑时间为 09:00),此时交易所尚未产生当天的开盘价(09:25 集合竞价产生)。如果在此时获取当日数据,属于未来数据。

Q3: 如果我不开启这个选项,回测结果一定不可信吗?
A3: 不一定,经验丰富的开发者可以通过严谨的代码逻辑避免未来数据。但开启此选项可以作为一道强制的保险,防止无意中引入未来数据,强烈建议在开发阶段始终开启。

Q4: get_fundamentals 查询财务数据时,传入 statDate 一定会导致未来数据吗?
A4: 是的,在回测中极大概率会导致未来数据。因为 statDate 是按财报统计周期(如 '2023q1')查询,系统会直接返回该季度的最终数据,忽略了该数据的实际发布日期(PubDate)。在回测中应始终使用 date 参数查询。