3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在聚宽(JoinQuant)量化交易平台上编写趋势跟踪策略时,未来函数和回撤失控是导致“回测是天堂,实盘是地狱”的两大核心杀手。要构建一个稳健的趋势策略,必须从系统设置、数据获取、订单撮合及风险控制四个维度进行闭环管理。
以下是彻底避免未来函数、控制回撤并保证回测与实盘一致性的完整指南及示例代码:
开启系统级防未来数据模式
在 initialize 中调用 set_option("avoid_future_data", True)。开启后,如果策略在盘中尝试获取当前时间之后的未来数据(例如在 09:30 获取当天的收盘价),系统会直接抛出 FutureDataError 异常,强行中断,帮助你在回测阶段就揪出隐蔽的未来函数。
开启动态复权(真实价格)模式
默认的传统前复权回测使用的是基于未来某一天的复权因子,这会引入未来函数。必须在 initialize 中设置 set_option('use_real_price', True)。这样回测时会使用历史当天的真实价格撮合,而通过 history 等 API 获取的历史数据则是基于当天日期的前复权价格,完美模拟实盘。
严格遵守数据获取的时间窗口
history 或 attribute_history 获取天数据时,默认不包含当天,这是安全的。get_price 或 get_bars,必须确保 end_date / end_dt 不大于 context.current_dt,且在获取当前未结束的 Bar 时,注意 include_now 参数的设置。建议在盘中只使用上一分钟或上一个交易日已收盘的数据。避免跨日期缓存 API 返回的价格数据
在真实价格模式下,不同日期看到的前复权历史价格可能因分红送股而不同。切勿将 history 等返回的 DataFrame 存入全局变量 g 中跨日使用。
StopMarketOrderStyle(停止单)或在代码中主动监控持仓,当价格跌破均线一定比例或从最高点回撤达到阈值时,果断清仓。set_option('order_volume_ratio', 0.25),限制单笔下单量不超过当日真实成交量的 25%,防止回测中出现不切实际的超大额瞬间成交。以下是一个标准的双均线趋势跟踪策略,融入了防未来函数设置、真实价格模式、以及严格的均线止损控制:
import jqdata
def initialize(context):
# 1. 设定基准
set_benchmark('000300.XSHG')
# 2. 开启防未来数据模式(核心:尝试获取未来数据时报错)
set_option("avoid_future_data", True)
# 3. 开启真实价格(动态复权)模式(核心:避免使用未来的复权因子)
set_option('use_real_price', True)
# 4. 限制成交量比例,防止回测撮合失真
set_option('order_volume_ratio', 0.25)
# 5. 设置佣金与印花税
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 全局变量
g.security = '000001.XSHE' # 平安银行
# 每日 09:30 运行
run_daily(market_open, time='09:30')
def market_open(context):
security = g.security
# 获取历史数据(不包含今天,安全无未来)
# 此时拿到的 close 是基于昨天收盘后的前复权价格
close_data = attribute_history(security, 20, '1d', ['close'])
# 计算 5 日均线和 20 日均线
ma5 = close_data['close'][-5:].mean()
ma20 = close_data['close'].mean()
# 获取当前最新已知价格(使用 get_current_data,安全无未来)
current_data = get_current_data()
current_price = current_data[security].last_price
# 排除停牌
if current_data[security].paused:
return
cash = context.portfolio.available_cash
keep_amount = context.portfolio.positions[security].closeable_amount
# 趋势买入信号:5日均线上穿20日均线,且当前无持仓
if ma5 > ma20 and keep_amount == 0:
order_value(security, cash)
log.info(f"触发多头趋势,买入 {security},价格: {current_price}")
# 趋势卖出/止损信号:当前价格跌破20日均线,且持有仓位
elif current_price < ma20 and keep_amount > 0:
order_target(security, 0)
log.info(f"跌破均线止损,卖出 {security},价格: {current_price}")
DataFrame.sample() 或遍历 dict,必须指定 random_state 或进行 sorted() 排序,防止因 Python 运行环境差异导致模拟盘与回测买入不同的股票。09:30 - 15:00 之间通过 run_daily 计划任务下单。set_option('match_with_order_book', True),使用真实的买一/卖一五档盘口进行撮合,使滑点表现更贴近实盘。