问题描述
解决方案
在聚宽(JoinQuant)平台实现“股票+期货”混合策略,核心在于使用 set_subportfolios 函数将账户资金划分为不同的子账户(SubPortfolio),并分别指定账户类型(stock 和 futures)。
在下单交易时,需要通过参数 pindex(Portfolio Index)来指定使用哪个子账户进行操作。
以下是具体的实现步骤和完整的策略代码示例。
实现步骤
- 初始化子账户:在
initialize函数中,计算股票和期货的资金分配,调用set_subportfolios创建两个子账户。pindex=0:设置为股票账户 (type='stock')。pindex=1:设置为期货账户 (type='futures')。
- 设置费率:分别设置股票和期货的手续费。
- 定时运行:
- 股票交易通常在
09:30等时间运行。 - 期货交易建议设置
reference_security(参考标的),以确保包含夜盘等交易时间段。
- 股票交易通常在
- 下单交易:
- 买卖股票时,指定
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
关键点说明
set_subportfolios: 这是实现多账户的核心。列表中的顺序决定了pindex的值。代码中第一个配置是股票,所以股票操作用pindex=0;第二个是期货,所以期货操作用pindex=1。pindex参数: 所有的下单函数(如order,order_value,order_target等)以及资金查询函数(如inout_cash)都需要指定pindex。如果不指定,默认是pindex=0。- 资金查询:
- 查询股票资金:
context.subportfolios[0].available_cash - 查询期货资金:
context.subportfolios[1].available_cash - 查询总资产:
context.portfolio.total_value(所有子账户之和)
- 查询股票资金:
- 持仓查询:
- 股票持仓:
context.subportfolios[0].long_positions - 期货持仓:
context.subportfolios[1].long_positions(多头) 和short_positions(空头)。
- 股票持仓:
- 运行时间: 期货通常有夜盘,如果策略涉及期货,建议在
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.portfolio 和 context.subportfolios 有什么区别?
A: context.subportfolios 是一个列表,包含所有子账户的详细信息。context.portfolio 是一个汇总对象,它的 total_value 等属性是所有子账户的总和。但在单账户模式下,context.portfolio 直接指向 subportfolios[0]。在多账户模式下,建议直接操作 context.subportfolios[i] 以避免混淆。