问题描述
解决方案
在量化交易回测中,“未来函数”(或未来数据)是策略开发者最大的敌人之一。它指的是在历史回测时,使用了当时时间点无法获取到的未来信息(例如在今天开盘时获取了今天的收盘价),从而导致回测收益虚高,实盘却表现惨淡。
在 JoinQuant(聚宽)平台上,识别和避免代码中的未来函数主要有以下几种方法:
1. 开启官方提供的“避免未来数据模式”
JoinQuant 提供了一个实验性的设置项,可以帮助开发者在代码运行层面自动检测和处理部分未来数据。你只需要在策略的 initialize 函数中加入以下代码:
def initialize(context):
# 开启避免未来数据模式
set_option("avoid_future_data", True)
开启后的处理机制:
- 抛出异常(FutureDataError): 对于可以通过时间参数避免的未来数据(例如在回测中使用包含时间参数的 API 如
get_bars去取current_dt之后的数据),系统会直接抛出FutureDataError异常,强制中断回测,提醒你修改代码。 - 自动剔除: 对于用户没法通过函数参数主动避免的未来数据(例如
get_call_auction),系统会将返回结果中的未来数据自动剔除掉,而不会抛出异常。
2. 审查常见的未来函数调用场景
虽然 avoid_future_data 能拦截很多问题,但它不是万能的。你需要人工审查代码中是否出现了以下常见错误:
- 盘前获取盘后数据: 在
before_trading_start(开盘前 9:00) 或market_open(开盘 9:30) 中,调用get_price获取了当天的close(收盘价)、high(最高价) 或low(最低价)。 - 财务数据的提前使用: 财务报表通常在季度结束后的一个月内发布。如果在回测中直接按季度末日期(如 3月31日)去取一季报数据,就引入了未来函数。应使用
get_fundamentals并传入date参数(系统会自动取该日期前已发布的最新财报),而不是直接传入statDate(除非你明确知道发布日期)。 - 动态复权(真实价格)模式未开启: 如果使用传统的前复权模式回测,历史时刻拿到的数据是基于未来某一天的前复权因子计算的,这本身就存在未来函数。强烈建议在
initialize中开启真实价格模式:set_option('use_real_price', True)。
3. 警惕外部数据和固定规则
- 引入外部数据: 如果你的策略读取了本地文件或网络数据,而这些数据在生成时包含了未来信息,平台是无法检测到的。
- 固定股票池/时间点: 在代码中硬编码了“历史上某段时间大涨的股票代码”并在低点买入,这种“上帝视角”也是一种隐性的未来函数。
总结
要彻底识别和消除未来函数,最佳实践是:
- 必须在
initialize中设置set_option("avoid_future_data", True)和set_option('use_real_price', True)。 - 严格区分策略运行的逻辑时间(
context.current_dt),确保任何数据获取 API 的end_date都不超过当前逻辑时间。 - 仔细检查财务数据和宏观数据的发布滞后性。