问题描述
解决方案
这是一个基于聚宽(JoinQuant)平台的期货主力合约自动移仓换月及交易策略。
策略说明
-
核心功能:
- 自动锁定主力:每日开盘前自动获取指定品种(如螺纹钢 RB)的当前主力合约代码。
- 自动移仓换月:如果主力合约发生切换(例如从 RB2305 切换到 RB2310),策略会自动平掉旧合约的持仓,并在新合约上开仓(或者等待新信号)。
- 交易逻辑:为了演示,这里使用经典的 双均线策略(金叉做多,死叉做空)。
- 短期均线 > 长期均线:平空做多。
- 短期均线 < 长期均线:平多做空。
-
注意事项:
- 期货交易需要在
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')
关键点解析
-
get_dominant_future(symbol):- 这是聚宽的核心函数。传入品种代码(如 'RB'),它会返回当前日期的主力合约代码(如 'RB2310.XSGE')。
- 主力合约通常是根据持仓量最大来判定的。
-
移仓换月逻辑:
- 在
market_open的开头,我们遍历context.portfolio.long_positions和short_positions。 - 只要发现持仓的合约代码 不等于 当天获取的
g.current_contract,就说明主力合约已经切换,或者我们持有的是非主力合约。 - 策略选择直接调用
order_target(security, 0, ...)平掉旧合约。 - 注:平仓后,下方的策略逻辑会根据均线状态在新的主力合约上重新开仓,从而完成“移仓”。
- 在
-
期货交易函数:
- 期货交易必须指定
side参数。 side='long':操作多单(买入开多,卖出平多)。side='short':操作空单(卖出开空,买入平空)。order_target(security, 0, side='long'):表示将多单持仓调整为0,即平掉所有多单。
- 期货交易必须指定
-
账户设置:
set_subportfolios([SubPortfolioConfig(..., type='futures')])是必须的,否则回测引擎会默认这是股票账户,导致无法买入期货合约。
Q&A
Q: 如何将策略改为交易股指期货(如沪深300)?
A: 只需将 initialize 函数中的 g.underlying_symbol = 'RB' 改为 g.underlying_symbol = 'IF' 即可。同时建议调整保证金比例(股指期货保证金通常较高)。
Q: 为什么移仓时是先平旧仓再开新仓,而不是直接平仓开仓同时进行?
A: 在代码逻辑中,先检查并平掉非主力合约,释放保证金。随后代码向下执行到信号判断部分,如果均线形态依然符合持仓条件,会在新合约上开仓。这样可以避免保证金不足的问题。
Q: 这个策略可以直接实盘吗?
A: 不可以。这是一个回测策略示例。实盘需要考虑更多细节,如滑点控制、网络延迟、撤单逻辑、资金管理以及交易所的平今仓手续费优化等。