🚀 AI 一键生成 joinquant 策略代码

如何在 JoinQuant 中同时交易股票和期货?子账户(SubPortfolio)应用指南

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

问题描述

我想写一个策略,一部分资金买股票,另一部分资金做期货,该怎么实现?

解决方案

在聚宽(JoinQuant)平台实现“股票+期货”混合策略,核心在于使用 set_subportfolios 函数将账户资金划分为不同的子账户(SubPortfolio),并分别指定账户类型(stockfutures)。

在下单交易时,需要通过参数 pindex(Portfolio Index)来指定使用哪个子账户进行操作。

以下是具体的实现步骤和完整的策略代码示例。

实现步骤

  1. 初始化子账户:在 initialize 函数中,计算股票和期货的资金分配,调用 set_subportfolios 创建两个子账户。
    • pindex=0:设置为股票账户 (type='stock')。
    • pindex=1:设置为期货账户 (type='futures')。
  2. 设置费率:分别设置股票和期货的手续费。
  3. 定时运行
    • 股票交易通常在 09:30 等时间运行。
    • 期货交易建议设置 reference_security(参考标的),以确保包含夜盘等交易时间段。
  4. 下单交易
    • 买卖股票时,指定 pindex=0
    • 买卖期货时,指定 pindex=1,并注意期货特有的 side(方向)参数。

策略代码示例

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

def initialize(context):
    """
    初始化函数
    """
    # 1. 设定基准(这里以沪深300为例)
    set_benchmark('000300.XSHG')
    
    # 2. 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 3. 资金分配与子账户设置
    # 获取总初始资金
    total_cash = context.portfolio.starting_cash
    # 设定 50% 资金做股票,50% 资金做期货
    stock_cash = total_cash * 0.5
    futures_cash = total_cash * 0.5
    
    # set_subportfolios 接受一个列表,列表索引即为 pindex
    # pindex=0: 股票账户
    # pindex=1: 期货账户
    set_subportfolios([
        SubPortfolioConfig(cash=stock_cash, type='stock'),
        SubPortfolioConfig(cash=futures_cash, type='futures')
    ])
    
    # 4. 设置手续费(股票和期货分开设置)
    # 股票手续费
    set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    # 期货手续费
    set_order_cost(OrderCost(open_tax=0, close_tax=0, open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023, min_commission=0), type='futures')
    
    # 5. 定义全局变量
    g.stock_security = '000001.XSHE' # 平安银行
    g.future_symbol = 'RB'           # 螺纹钢品种代码
    
    # 6. 设置定时运行
    # 股票交易逻辑:每天 09:30 运行
    run_daily(trade_stocks, time='09:30')
    
    # 期货交易逻辑:每天开盘前运行(为了获取主力合约)和盘中交易
    # 注意:期货策略建议设置 reference_security 为对应的主力合约或指数,以匹配期货交易时间(含夜盘)
    run_daily(trade_futures, time='09:30', reference_security='RB9999.XSGE')

def trade_stocks(context):
    """
    股票交易逻辑
    """
    security = g.stock_security
    # 获取 pindex=0 (股票账户) 的可用资金
    # 注意:context.subportfolios[0] 对应股票账户
    cash = context.subportfolios[0].available_cash
    
    # 简单的示例逻辑:如果没持仓就全仓买入
    # 获取 pindex=0 的持仓信息
    current_position = context.subportfolios[0].long_positions.get(security)
    
    if current_position is None and cash > 0:
        log.info("股票账户(pindex=0) - 买入股票: %s" % security)
        # 关键点:下单时必须指定 pindex=0
        order_value(security, cash, pindex=0)

def trade_futures(context):
    """
    期货交易逻辑
    """
    # 获取当月主力合约
    dom_contract = get_dominant_future(g.future_symbol)
    if not dom_contract:
        return
        
    # 获取 pindex=1 (期货账户) 的可用资金
    cash = context.subportfolios[1].available_cash
    
    # 获取 pindex=1 的多头持仓
    long_positions = context.subportfolios[1].long_positions
    
    # 简单的示例逻辑:如果没有多单,则开多 1 手
    if dom_contract not in long_positions:
        log.info("期货账户(pindex=1) - 开多合约: %s" % dom_contract)
        # 关键点:下单时必须指定 pindex=1,且期货需要指定 side (long/short)
        order(dom_contract, 1, side='long', pindex=1)
    else:
        # 如果有持仓,平仓演示
        # log.info("期货账户(pindex=1) - 平多合约: %s" % dom_contract)
        # order_target(dom_contract, 0, side='long', pindex=1)
        pass

def handle_data(context, data):
    """
    按分钟回测时每分钟调用,按天回测时每天调用
    """
    pass

关键点说明

  1. set_subportfolios: 这是实现多账户的核心。列表中的顺序决定了 pindex 的值。代码中第一个配置是股票,所以股票操作用 pindex=0;第二个是期货,所以期货操作用 pindex=1
  2. pindex 参数: 所有的下单函数(如 order, order_value, order_target 等)以及资金查询函数(如 inout_cash)都需要指定 pindex。如果不指定,默认是 pindex=0
  3. 资金查询:
    • 查询股票资金:context.subportfolios[0].available_cash
    • 查询期货资金:context.subportfolios[1].available_cash
    • 查询总资产:context.portfolio.total_value (所有子账户之和)
  4. 持仓查询:
    • 股票持仓:context.subportfolios[0].long_positions
    • 期货持仓:context.subportfolios[1].long_positions (多头) 和 short_positions (空头)。
  5. 运行时间: 期货通常有夜盘,如果策略涉及期货,建议在 run_daily 中指定 reference_security 为期货代码(如 RB9999.XSGE),这样回测引擎会按照期货的交易时间轴来触发事件。

Q&A

Q: 如果我想在回测中途调整股票和期货的资金比例,比如从50/50变成70/30,该怎么做?
A: 可以使用 transfer_cash(from_pindex, to_pindex, cash) 函数。例如,从期货账户(pindex=1)转账 100,000 元到股票账户(pindex=0),可以调用 transfer_cash(1, 0, 100000)

Q: 为什么我在期货下单时报错 "SecurityNotExist"?
A: 请检查合约代码是否正确。期货主力合约代码通常会随时间变化(如 RB1801 变为 RB1805),建议使用 get_dominant_future('RB') 动态获取当前日期的主力合约代码,而不是硬编码具体的数字合约。

Q: context.portfoliocontext.subportfolios 有什么区别?
A: context.subportfolios 是一个列表,包含所有子账户的详细信息。context.portfolio 是一个汇总对象,它的 total_value 等属性是所有子账户的总和。但在单账户模式下,context.portfolio 直接指向 subportfolios[0]。在多账户模式下,建议直接操作 context.subportfolios[i] 以避免混淆。