🚀 AI 一键生成 joinquant 策略代码

如何利用布林带在 JoinQuant 平台构建商品期货的突破交易策略?

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

问题描述

请为商品期货编写一个布林带通道突破策略。

解决方案

这是一个基于JoinQuant(聚宽)平台的商品期货布林带(Bollinger Bands)突破策略。

策略逻辑

  1. 标的:以螺纹钢(RB)主力合约为例。
  2. 指标:计算N日的移动平均线(中轨),以及标准差。上轨 = 中轨 + K * 标准差,下轨 = 中轨 - K * 标准差。
  3. 开仓条件
    • 做多:当价格突破上轨时,平空仓并开多仓。
    • 做空:当价格跌破下轨时,平多仓并开空仓。
  4. 平仓条件(回归中轨)
    • 持有多单时,价格跌破中轨,平多仓。
    • 持有空单时,价格升破中轨,平空仓。
  5. 移仓换月:自动获取主力合约,当主力合约切换时,策略会自动在新合约上交易(旧合约平仓逻辑需根据实际需求处理,本示例简化为只在当前主力上操作)。

策略代码

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

def initialize(context):
    """
    初始化函数,设定基准、滑点、手续费、期货账户等
    """
    # 设定基准(这里随意设定一个,期货策略主要看绝对收益)
    set_benchmark('000300.XSHG')
    
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 设定期货账户
    # 初始资金 500,000
    init_cash = 500000
    set_subportfolios([SubPortfolioConfig(cash=init_cash, type='futures')])
    
    # 设定期货手续费(以螺纹钢为例,万分之一)
    set_order_cost(OrderCost(open_commission=0.0001, close_commission=0.0001, close_today_commission=0.0001), type='futures')
    
    # 设定滑点
    set_slippage(PriceRelatedSlippage(0.002), type='futures')
    
    # --- 策略参数设置 ---
    g.underlying_symbol = 'RB' # 标的品种:螺纹钢
    g.n = 20                   # 布林带窗口周期
    g.k = 2.0                  # 布林带宽度(标准差倍数)
    g.trade_lot = 2            # 每次交易手数
    
    # 每天开盘时运行
    run_daily(trade_func, time='open')

def trade_func(context):
    """
    交易主逻辑函数
    """
    # 1. 获取当前主力合约
    dom_contract = get_dominant_future(g.underlying_symbol)
    
    # 如果获取不到主力合约(比如上市初期或数据缺失),则跳过
    if not dom_contract:
        return

    # 2. 获取历史数据
    # 取过去 N + 2 天的数据,保证计算指标足够
    # fields包含 'close', 'high', 'low'
    data = attribute_history(dom_contract, g.n + 2, '1d', ['close'])
    
    # 如果数据长度不足,无法计算指标,跳过
    if len(data) < g.n:
        return
    
    # 3. 计算布林带指标
    close_prices = data['close'].values
    
    # 计算移动平均(中轨)
    # 使用 pandas 的 rolling 函数计算
    df_close = pd.Series(close_prices)
    ma = df_close.rolling(window=g.n).mean()
    std = df_close.rolling(window=g.n).std()
    
    # 获取最新的指标值(昨天收盘的数据,用于决定今天的操作)
    # 注意:attribute_history 获取的是不包含今天的数据(如果是按天回测且在open运行)
    current_ma = ma.iloc[-1]
    current_std = std.iloc[-1]
    
    up_band = current_ma + g.k * current_std   # 上轨
    dn_band = current_ma - g.k * current_std   # 下轨
    
    # 获取昨日收盘价作为信号判断依据
    last_close = close_prices[-1]
    
    # 4. 获取当前持仓情况
    # long_positions 和 short_positions 是字典,key是合约代码
    long_pos = context.portfolio.long_positions.get(dom_contract)
    short_pos = context.portfolio.short_positions.get(dom_contract)
    
    has_long = long_pos and long_pos.total_amount > 0
    has_short = short_pos and short_pos.total_amount > 0
    
    # 5. 交易逻辑
    
    # --- 开仓/反手逻辑 ---
    
    # 价格突破上轨,做多
    if last_close > up_band:
        # 如果有空单,先平空
        if has_short:
            log.info("价格突破上轨,平空仓: %s" % dom_contract)
            order_target(dom_contract, 0, side='short')
        
        # 如果没有多单,开多
        if not has_long:
            log.info("价格突破上轨,开多仓: %s" % dom_contract)
            order(dom_contract, g.trade_lot, side='long')
            
    # 价格跌破下轨,做空
    elif last_close < dn_band:
        # 如果有多单,先平多
        if has_long:
            log.info("价格跌破下轨,平多仓: %s" % dom_contract)
            order_target(dom_contract, 0, side='long')
            
        # 如果没有空单,开空
        if not has_short:
            log.info("价格跌破下轨,开空仓: %s" % dom_contract)
            order(dom_contract, g.trade_lot, side='short')
            
    # --- 止盈/回归中轨平仓逻辑 ---
    
    # 持有多单,价格跌破中轨,平多
    elif has_long and last_close < current_ma:
        log.info("多单回归中轨,平多仓: %s" % dom_contract)
        order_target(dom_contract, 0, side='long')
        
    # 持有空单,价格升破中轨,平空
    elif has_short and last_close > current_ma:
        log.info("空单回归中轨,平空仓: %s" % dom_contract)
        order_target(dom_contract, 0, side='short')

    # 6. 移仓换月处理 (简单版)
    # 检查持仓中是否有非主力合约,如果有,则平仓
    for security in list(context.portfolio.long_positions.keys()):
        if security != dom_contract:
            order_target(security, 0, side='long')
            log.info("移仓换月,平旧合约多单: %s" % security)
            
    for security in list(context.portfolio.short_positions.keys()):
        if security != dom_contract:
            order_target(security, 0, side='short')
            log.info("移仓换月,平旧合约空单: %s" % security)

代码要点解析

  1. 期货账户设置

    • 必须在 initialize 中使用 set_subportfolios 将账户类型设置为 futures,否则无法进行期货交易。
    • 使用 set_order_cost 设置期货特定的手续费结构。
  2. 主力合约获取

    • 使用 get_dominant_future('RB') 动态获取当前的主力合约代码(如 RB2310.XSGE)。这是期货策略的关键,因为合约会到期。
  3. 数据获取

    • 使用 attribute_history 获取历史收盘价。这里设置为 g.n + 2 是为了确保计算滚动平均值时有足够的数据点。
  4. 交易指令

    • 期货交易需要指定方向 side 参数:'long' (多) 或 'short' (空)。
    • order(security, amount, side='long'):开多单。
    • order_target(security, 0, side='short'):平空单(将空单仓位调整为0)。
  5. 移仓换月

    • 代码末尾包含了一个简单的循环,检查当前持仓是否为主力合约。如果持仓合约不再是主力合约(get_dominant_future 返回了新合约),则平掉旧合约的仓位。

Q&A

Q: 如何修改策略的交易频率(例如改为分钟级)?
A: 将 run_daily(trade_func, time='open') 修改为 run_daily(trade_func, time='every_bar'),并在回测设置中选择“分钟回测”。同时,attribute_history 中的 '1d' 参数可能需要改为 '1m' 或其他分钟周期,并相应调整布林带的参数 g.n

Q: 为什么使用 order_target 为 0 来平仓?
A: order_target 是一个目标仓位函数。将目标设为 0 是最简单的平仓方式,它会自动计算需要卖出或买入多少手来使持仓归零,避免了手动计算可用仓位的麻烦。

Q: 策略中的 use_real_price 是什么意思?
A: set_option('use_real_price', True) 表示使用真实价格(动态复权)。在期货回测中,这非常重要,因为它能更真实地模拟账户资金变动和保证金占用情况。如果不开启,回测系统可能会使用前复权价格,导致资金计算与实际严重不符。