🚀 AI 一键生成 joinquant 策略代码

如何编写 JoinQuant 期货策略以自动跟踪并交易主力合约?

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

问题描述

请编写一个能够自动跟踪期货主力合约并进行交易的策略。

解决方案

这是一个基于聚宽(JoinQuant)平台的期货主力合约自动移仓换月及交易策略。

策略说明

  1. 核心功能

    • 自动锁定主力:每日开盘前自动获取指定品种(如螺纹钢 RB)的当前主力合约代码。
    • 自动移仓换月:如果主力合约发生切换(例如从 RB2305 切换到 RB2310),策略会自动平掉旧合约的持仓,并在新合约上开仓(或者等待新信号)。
    • 交易逻辑:为了演示,这里使用经典的 双均线策略(金叉做多,死叉做空)
      • 短期均线 > 长期均线:平空做多。
      • 短期均线 < 长期均线:平多做空。
  2. 注意事项

    • 期货交易需要在 initialize 中设置账户类型为 futures
    • 主力合约的获取使用聚宽特有的 API get_dominant_future

策略代码

# -*- coding: utf-8 -*-
from jqdata import *

def initialize(context):
    """
    初始化函数,设定基准、账户类型、全局变量等
    """
    # 1. 设定基准(这里用沪深300,也可以设为0)
    set_benchmark('000300.XSHG')
    
    # 2. 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 3. 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 4. 设定期货账户
    # 必须将账户类型设定为 futures,否则无法交易期货
    set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='futures')])
    
    # 5. 设定期货保证金比例(可选,这里设为15%)
    set_option('futures_margin_rate', 0.15)
    
    # --- 策略参数设置 ---
    # 设定要操作的期货品种代码(例如:RB 表示螺纹钢,IF 表示沪深300股指期货)
    g.underlying_symbol = 'RB' 
    
    # 均线参数
    g.short_window = 5   # 短期均线窗口
    g.long_window = 20   # 长期均线窗口
    
    # 记录当前操作的主力合约
    g.current_contract = None
    
    # 6. 设定定时运行
    # 开盘前运行:确认主力合约
    run_daily(before_market_open, time='before_open', reference_security='RB9999.XSGE')
    # 开盘时运行:交易逻辑
    run_daily(market_open, time='open', reference_security='RB9999.XSGE')

def before_market_open(context):
    """
    开盘前运行函数:每日更新主力合约
    """
    # 获取当前日期的主力合约代码
    # get_dominant_future 返回如 'RB2310.XSGE'
    dom_contract = get_dominant_future(g.underlying_symbol)
    
    # 如果主力合约发生了变化(换月)
    if g.current_contract and g.current_contract != dom_contract:
        log.info("主力合约切换: %s -> %s" % (g.current_contract, dom_contract))
        # 具体的移仓操作在 market_open 中通过检查持仓来执行
        
    g.current_contract = dom_contract
    log.info("当前主力合约: %s" % g.current_contract)

def market_open(context):
    """
    开盘交易函数
    """
    if g.current_contract is None:
        return
    
    # --- 1. 移仓换月逻辑 ---
    # 检查当前持仓,如果持有的合约不是当前主力合约,则强制平仓
    # 注意:这里是简单的平旧仓,新仓位由下面的策略逻辑决定是否开仓
    
    # 获取多单持仓
    long_positions = context.portfolio.long_positions
    for security in list(long_positions.keys()):
        if security != g.current_contract:
            log.info("平掉旧合约多单: %s" % security)
            order_target(security, 0, side='long')
            
    # 获取空单持仓
    short_positions = context.portfolio.short_positions
    for security in list(short_positions.keys()):
        if security != g.current_contract:
            log.info("平掉旧合约空单: %s" % security)
            order_target(security, 0, side='short')

    # --- 2. 获取行情数据 ---
    # 获取过去 g.long_window + 1 天的收盘价
    # 注意:使用主力合约代码获取数据
    hist = attribute_history(g.current_contract, g.long_window + 1, '1d', ['close'])
    
    # 如果数据长度不够,不进行计算
    if len(hist) < g.long_window:
        return
        
    close_prices = hist['close']
    
    # --- 3. 计算指标 ---
    # 计算短期均线
    ma_short = close_prices[-g.short_window:].mean()
    # 计算长期均线
    ma_long = close_prices[-g.long_window:].mean()
    
    # 获取当前价格
    current_price = close_prices[-1]
    
    # --- 4. 获取当前主力合约的持仓情况 ---
    # 多单数量
    long_amount = context.portfolio.long_positions[g.current_contract].total_amount
    # 空单数量
    short_amount = context.portfolio.short_positions[g.current_contract].total_amount
    
    # --- 5. 交易信号判断与执行 ---
    
    # 信号:金叉 (短均线 > 长均线) -> 做多
    if ma_short > ma_long:
        # 如果有空单,先平空
        if short_amount > 0:
            log.info("金叉:平空单")
            order_target(g.current_contract, 0, side='short')
        
        # 如果没有多单,开多单
        if long_amount == 0:
            log.info("金叉:开多单")
            # 按价值下单,例如使用当前可用资金的 50%
            # 注意:期货order_value计算的是保证金,这里简化处理,实际建议按手数下单
            # 这里演示开 2 手
            order(g.current_contract, 2, side='long')
            
    # 信号:死叉 (短均线 < 长均线) -> 做空
    elif ma_short < ma_long:
        # 如果有多单,先平多
        if long_amount > 0:
            log.info("死叉:平多单")
            order_target(g.current_contract, 0, side='long')
            
        # 如果没有空单,开空单
        if short_amount == 0:
            log.info("死叉:开空单")
            # 演示开 2 手
            order(g.current_contract, 2, side='short')

关键点解析

  1. get_dominant_future(symbol):

    • 这是聚宽的核心函数。传入品种代码(如 'RB'),它会返回当前日期的主力合约代码(如 'RB2310.XSGE')。
    • 主力合约通常是根据持仓量最大来判定的。
  2. 移仓换月逻辑:

    • market_open 的开头,我们遍历 context.portfolio.long_positionsshort_positions
    • 只要发现持仓的合约代码 不等于 当天获取的 g.current_contract,就说明主力合约已经切换,或者我们持有的是非主力合约。
    • 策略选择直接调用 order_target(security, 0, ...) 平掉旧合约。
    • 注:平仓后,下方的策略逻辑会根据均线状态在新的主力合约上重新开仓,从而完成“移仓”。
  3. 期货交易函数:

    • 期货交易必须指定 side 参数。
    • side='long':操作多单(买入开多,卖出平多)。
    • side='short':操作空单(卖出开空,买入平空)。
    • order_target(security, 0, side='long'):表示将多单持仓调整为0,即平掉所有多单。
  4. 账户设置:

    • set_subportfolios([SubPortfolioConfig(..., type='futures')]) 是必须的,否则回测引擎会默认这是股票账户,导致无法买入期货合约。

Q&A

Q: 如何将策略改为交易股指期货(如沪深300)?
A: 只需将 initialize 函数中的 g.underlying_symbol = 'RB' 改为 g.underlying_symbol = 'IF' 即可。同时建议调整保证金比例(股指期货保证金通常较高)。

Q: 为什么移仓时是先平旧仓再开新仓,而不是直接平仓开仓同时进行?
A: 在代码逻辑中,先检查并平掉非主力合约,释放保证金。随后代码向下执行到信号判断部分,如果均线形态依然符合持仓条件,会在新合约上开仓。这样可以避免保证金不足的问题。

Q: 这个策略可以直接实盘吗?
A: 不可以。这是一个回测策略示例。实盘需要考虑更多细节,如滑点控制、网络延迟、撤单逻辑、资金管理以及交易所的平今仓手续费优化等。