🚀 AI 一键生成 joinquant 策略代码

JoinQuant 分钟级回测:如何构建一个日内均值回归交易策略?

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

问题描述

请帮我编写一个在分钟线上运行的日内均值回归策略。

解决方案

这是一个基于**布林带(Bollinger Bands)**的日内均值回归策略。

策略逻辑

  1. 核心思想:价格总是围绕均值波动。当价格偏离均值过大(突破上轨或下轨)时,认为价格会回归均值。
  2. 标的:选择 股指期货(如 IF9999.CCFX)
    • 原因:A股股票实行T+1制度,无法当天买入当天卖出,且融券做空困难。期货支持T+0和双向交易,最适合日内均值回归策略。
  3. 指标计算
    • 中轨:过去 N 分钟的收盘价移动平均线(MA)。
    • 上轨:中轨 + K倍标准差。
    • 下轨:中轨 - K倍标准差。
  4. 交易信号
    • 开多:价格跌破下轨(超卖)。
    • 开空:价格突破上轨(超买)。
    • 平仓:价格回归到中轨附近。
  5. 风控:每日收盘前(14:55)强制平仓,不持仓过夜。

策略代码

# -*- coding: utf-8 -*-
from jqdata import *
import numpy as np

def initialize(context):
    """
    初始化函数,设定基准、标的、参数等
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 设定操作标的:沪深300股指期货主力合约
    # 注意:日内回测建议使用期货,因为股票是T+1,无法日内回转
    g.security = 'IF9999.CCFX'
    
    # 策略参数
    g.n = 20      # 均线窗口长度(20分钟)
    g.k = 2.0     # 标准差倍数
    g.open_num = 1 # 每次开仓手数
    
    # 设定账户类型为期货账户,并给予初始资金
    set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='futures')])
    
    # 设定期货保证金比例(根据实际情况调整,这里设为15%)
    set_option('futures_margin_rate', 0.15)
    
    # 运行频率:每分钟运行一次
    run_daily(trade_func, time='every_bar')
    
    # 每日收盘前强制平仓,确保日内策略不留隔夜仓
    run_daily(close_all, time='14:55')

def trade_func(context):
    """
    交易主逻辑,每分钟执行一次
    """
    # 获取当前时间
    current_time = context.current_dt.time()
    
    # 避免在刚开盘数据不足时或收盘前最后几分钟交易
    if str(current_time) < '09:30:00' or str(current_time) > '14:50:00':
        return

    security = g.security
    
    # 获取过去 g.n 分钟的收盘价数据
    # include_now=True 表示包含当前这一分钟的实时bar(回测中)
    # 如果是实盘,建议根据需求调整,通常使用过去N个已完成的bar
    data = get_bars(security, count=g.n, unit='1m', fields=['close'], include_now=False)
    
    if data is None or len(data) < g.n:
        return
        
    close_prices = data['close']
    
    # 计算布林带
    mid_line = close_prices.mean()           # 中轨:均线
    std = close_prices.std()                 # 标准差
    up_line = mid_line + g.k * std           # 上轨
    low_line = mid_line - g.k * std          # 下轨
    
    # 获取当前最新价格
    current_data = get_current_data()
    current_price = current_data[security].last_price
    
    # 获取当前持仓情况
    # long_positions: 多单仓位
    # short_positions: 空单仓位
    long_pos = context.portfolio.long_positions[security]
    short_pos = context.portfolio.short_positions[security]
    
    # ---------------------------------------------------
    # 交易逻辑
    # ---------------------------------------------------
    
    # 1. 平仓逻辑 (回归均值即平仓)
    # 如果持有多单,且价格回归到中轨上方,平多
    if long_pos.total_amount > 0 and current_price >= mid_line:
        order_target(security, 0, side='long')
        log.info("价格回归中轨,平多单。当前价: %.2f, 中轨: %.2f" % (current_price, mid_line))
        
    # 如果持有空单,且价格回归到中轨下方,平空
    if short_pos.total_amount > 0 and current_price <= mid_line:
        order_target(security, 0, side='short')
        log.info("价格回归中轨,平空单。当前价: %.2f, 中轨: %.2f" % (current_price, mid_line))

    # 2. 开仓逻辑 (突破轨道即逆势开仓)
    # 如果没有持仓
    if long_pos.total_amount == 0 and short_pos.total_amount == 0:
        # 价格跌破下轨,看多(均值回归),开多单
        if current_price < low_line:
            order(security, g.open_num, side='long')
            log.info("价格跌破下轨,开多单。当前价: %.2f, 下轨: %.2f" % (current_price, low_line))
            
        # 价格突破上轨,看空(均值回归),开空单
        elif current_price > up_line:
            order(security, g.open_num, side='short')
            log.info("价格突破上轨,开空单。当前价: %.2f, 上轨: %.2f" % (current_price, up_line))

def close_all(context):
    """
    收盘前强制平仓函数
    """
    security = g.security
    long_pos = context.portfolio.long_positions[security]
    short_pos = context.portfolio.short_positions[security]
    
    if long_pos.total_amount > 0:
        order_target(security, 0, side='long')
        log.info("收盘强制平多单")
        
    if short_pos.total_amount > 0:
        order_target(security, 0, side='short')
        log.info("收盘强制平空单")

代码关键点解析

  1. 标的选择 (IF9999.CCFX)

    • 代码中使用了沪深300股指期货主力合约。这是因为日内策略要求T+0(当天开仓当天平),而A股股票是T+1。如果你必须使用股票,需要你有底仓(即昨天已经买入该股票),通过做T的方式实现日内交易。
  2. 数据获取 (get_bars)

    • 使用 get_bars 获取过去20分钟的K线数据。
    • include_now=False 表示我们使用过去20个已完成的分钟Bar来计算指标,避免当前分钟价格波动导致信号闪烁。
  3. 布林带计算

    • 利用 numpypandas 的特性,直接对获取的数组进行 .mean().std() 计算,效率较高。
  4. 交易指令 (order / order_target)

    • 期货交易需要指定 side 参数。side='long' 操作多单,side='short' 操作空单。
    • order_target(security, 0, side='...') 用于将仓位调整为0,即平仓。
  5. 强制平仓 (close_all)

    • 日内策略最重要的是风险控制。我们在 14:55 触发强制平仓,防止因忘记平仓而持有隔夜风险。

如何在聚宽上运行

  1. 登录聚宽官网,进入“我的策略” -> “新建策略”。
  2. 将上述代码复制粘贴到代码编辑区。
  3. 设置回测参数:
    • 开始/结束日期:建议选择近期的一个月进行测试(分钟级回测速度较慢)。
    • 频率:必须选择 分钟
    • 初始资金:建议 500,000 或以上(股指期货一手保证金较高)。
  4. 点击“编译运行”或“运行回测”。

常见问题 Q&A

Q: 为什么不用股票做这个策略?
A: 股票是T+1交易,如果你上午买入,下午即使触发了卖出信号也无法卖出。除非你使用“底仓做T”的逻辑(即持有老股,买入新股,卖出老股),但这会使代码逻辑复杂很多。

Q: 为什么回测没有交易?
A: 可能是资金不足。股指期货一手合约价值较高(例如IF合约价值约100万,保证金15%约需15万)。如果初始资金设置太少(如1万),会导致下单失败。请检查日志中的 Order Rejected 信息。

Q: 如何调整策略的敏感度?
A: 修改 g.n(周期)和 g.k(标准差倍数)。

  • 减小 g.k(如改为1.5):交易更频繁,但假信号可能增加。
  • 增大 g.k(如改为2.5):交易更少,信号更可靠,但可能错过机会。