🚀 AI 一键生成 joinquant 策略代码
立即体验

聚宽平台编写趋势跟踪策略:如何彻底避免未来函数与控制回撤?

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

问题描述

聚宽稳健趋势策略如何避免未来函数?
在聚宽编写趋势跟踪策略时,如何检查并避免未来函数,同时控制回撤并保证回测逻辑与实盘一致?

解决方案

在聚宽(JoinQuant)量化交易平台上编写趋势跟踪策略时,未来函数回撤失控是导致“回测是天堂,实盘是地狱”的两大核心杀手。要构建一个稳健的趋势策略,必须从系统设置、数据获取、订单撮合及风险控制四个维度进行闭环管理。

以下是彻底避免未来函数、控制回撤并保证回测与实盘一致性的完整指南及示例代码:

一、 彻底避免未来函数的四大铁律

  1. 开启系统级防未来数据模式
    initialize 中调用 set_option("avoid_future_data", True)。开启后,如果策略在盘中尝试获取当前时间之后的未来数据(例如在 09:30 获取当天的收盘价),系统会直接抛出 FutureDataError 异常,强行中断,帮助你在回测阶段就揪出隐蔽的未来函数。

  2. 开启动态复权(真实价格)模式
    默认的传统前复权回测使用的是基于未来某一天的复权因子,这会引入未来函数。必须在 initialize 中设置 set_option('use_real_price', True)。这样回测时会使用历史当天的真实价格撮合,而通过 history 等 API 获取的历史数据则是基于当天日期的前复权价格,完美模拟实盘。

  3. 严格遵守数据获取的时间窗口

    • 使用 historyattribute_history 获取天数据时,默认不包含当天,这是安全的。
    • 如果使用 get_priceget_bars,必须确保 end_date / end_dt 不大于 context.current_dt,且在获取当前未结束的 Bar 时,注意 include_now 参数的设置。建议在盘中只使用上一分钟或上一个交易日已收盘的数据。
  4. 避免跨日期缓存 API 返回的价格数据
    在真实价格模式下,不同日期看到的前复权历史价格可能因分红送股而不同。切勿将 history 等返回的 DataFrame 存入全局变量 g 中跨日使用。


二、 趋势策略控制回撤的实用方法

  1. 引入移动止损/主动止损机制
    趋势策略必须配合止损。可以使用聚宽的 StopMarketOrderStyle(停止单)或在代码中主动监控持仓,当价格跌破均线一定比例或从最高点回撤达到阈值时,果断清仓。
  2. 限制单笔交易成交量占比
    设置 set_option('order_volume_ratio', 0.25),限制单笔下单量不超过当日真实成交量的 25%,防止回测中出现不切实际的超大额瞬间成交。

三、 避坑趋势策略源码示例(Python3)

以下是一个标准的双均线趋势跟踪策略,融入了防未来函数设置、真实价格模式、以及严格的均线止损控制:

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),使用真实的买一/卖一五档盘口进行撮合,使滑点表现更贴近实盘。