问题描述
解决方案
在聚宽(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中,只能使用前一个交易日及之前的数据。 - 使用
history或attribute_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) 功能强大,但它无法检测以下情况:
- 外部数据:如果您通过自定义库或文件导入了包含未来信息的外部数据(如本地 CSV 文件),系统无法检测。
- 硬编码逻辑:如果在代码中直接写死了未来的日期或特定的高低点位(例如:
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 参数查询。