3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在聚宽(JoinQuant)平台上开发复杂策略(如 ETF 轮动、多因子选股)时,回测出现“多年数百倍”的超高收益往往令人兴奋,但资深交易员的第一反应通常是质疑。这种异常高收益大概率是由**未来函数、不切实际的交易摩擦设置、过拟合(Overfitting)**导致的。
以下是系统性排查策略漏洞、识别过拟合风险并设计至少半年样本外验证的完整指南:
未来函数是回测高收益的“头号杀手”,即在历史回测的 T 日使用了 T 日收盘后甚至 T+N 日才已知的数据。
initialize 中开启避免未来数据模式:set_option("avoid_future_data", True)
开启后,若策略尝试获取未来数据,系统会抛出 FutureDataError。get_price、get_bars 时,确保 end_date 或 end_dt 不超过当前逻辑时间 context.current_dt。在日级策略中,使用 history 或 attribute_history 获取天数据时,默认是不包含当天的,这能有效避免未来函数。get_fundamentals 时,若传入 statDate(如指定季度),可能会引入未来函数(因为财报披露有滞后性)。建议使用 date 参数查询,系统会自动寻找该日期前已披露的最新财报。默认的摩擦设置可能过于宽松,导致高频调仓的 ETF 轮动策略在回测中几乎没有成本。
# 设置场内基金(ETF)的手续费:买入万三,卖出万三,无印花税,最低5元
set_order_cost(OrderCost(open_tax=0, close_tax=0, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='fund')
PriceRelatedSlippage(0.00246) 可能不够。建议针对 ETF 设置更严格的滑点:# 设置基金类滑点为 0.2%(双边)
set_slippage(PriceRelatedSlippage(0.002), type='fund')
initialize 中调用 set_option('use_real_price', True)。如果不开启,回测使用的是静态前复权价格,买入数量和持仓计算会与历史真实情况产生偏差,甚至引入未来复权因子导致的未来函数。过拟合是指策略过度拟合了历史回测时段的噪音,导致在历史表现极佳,但实盘(或样本外)迅速失效。
为了客观评估策略的真实盈利能力,必须将历史数据严格划分为训练集(样本内)和测试集(样本外)。
假设我们有 2018 年至 2024 年的数据:
对于复杂的轮动策略,推荐使用滚动窗口进行验证:
以下是一个标准的聚宽 ETF 轮动策略模板,内置了严格的摩擦成本、防未来函数设置,并展示了如何编写规范的初始化结构:
import jqdata
import pandas as pd
import numpy as np
def initialize(context):
# 1. 开启真实价格模式(防未来函数核心)
set_option('use_real_price', True)
# 2. 开启避免未来数据模式
set_option("avoid_future_data", True)
# 3. 设置基准
set_benchmark('000300.XSHG')
# 4. 严格的交易税费设置(针对场内基金/ETF)
# 买入万三,卖出万三,无印花税,最低5元
set_order_cost(OrderCost(
open_tax=0,
close_tax=0,
open_commission=0.0003,
close_commission=0.0003,
min_commission=5
), type='fund')
# 5. 严格的滑点设置(设置 0.2% 的价格滑点,模拟真实冲击成本)
set_slippage(PriceRelatedSlippage(0.002), type='fund')
# 策略参数(样本内调优确定的参数,在样本外测试时切勿修改)
g.momentum_window = 20 # 动量观测窗口
g.etf_pool = [
'510300.XSHG', # 沪深300ETF
'510500.XSHG', # 中证500ETF
'159915.XSHE', # 创业板ETF
'511880.XSHG' # 银华日利(避险货币基金)
]
# 定时运行:每周第一个交易日开盘运行
run_weekly(handle_etf_rotation, weekday=1, time='09:30')
def handle_etf_rotation(context):
# 获取历史数据,确保不包含当天,防止未来函数
# 结束时间设为前一交易日
prev_date = context.previous_date
scores = {}
for etf in g.etf_pool:
if etf == '511880.XSHG':
continue
# 获取过去 N 天的收盘价
hist = attribute_history(etf, g.momentum_window, '1d', ['close'], df=False, fq='pre')
if len(hist['close']) < g.momentum_window:
continue
# 计算动量得分:(当前价 - N天前价) / N天前价
score = (hist['close'][-1] - hist['close'][0]) / hist['close'][0]
scores[etf] = score
if not scores:
return
# 找出动量最强的 ETF
best_etf = max(scores, key=scores.get)
# 如果最强 ETF 的动量为正,则买入;否则买入货币基金避险
target_etf = best_etf if scores[best_etf] > 0 else '511880.XSHG'
# 调仓执行
current_positions = context.portfolio.long_positions.keys()
for position in current_positions:
if position != target_etf:
order_target(position, 0)
log.info(f"卖出避险或调仓: {position}")
if target_etf not in current_positions:
cash = context.portfolio.available_cash
if cash > 0:
order_value(target_etf, cash)
log.info(f"买入强势标的: {target_etf}")
当你在聚宽上看到令人难以置信的业绩时,请遵循以下三步:
avoid_future_data,检查是否有代码在盘中获取了当日收盘价。