问题描述
解决方案
在聚宽(JoinQuant)平台中,获取期货持仓量(Open Interest)主要通过数据获取接口中的 open_interest 字段来实现。持仓量是期货交易中判断多空分歧和资金流向的重要指标。
以下是获取持仓量的方法说明及一个完整的策略示例。
1. 获取持仓量的方法
在聚宽中,主要有以下几种方式获取持仓量:
- 历史/实时Bar数据 (
attribute_history/get_price/get_bars):- 字段名:
'open_interest' - 适用场景:策略逻辑判断,获取过去N天或N分钟的持仓量序列。
- 字段名:
- Tick数据 (
get_current_tick):- 属性名:
.position(注意:在Tick对象中,持仓量属性名为position) - 适用场景:高频策略或需要盘口实时持仓量。
- 属性名:
2. 策略示例:基于“量价配合”的期货趋势策略
策略逻辑:
该策略基于经典的量价分析理论。
- 多头信号:价格上涨 且 持仓量增加。这通常意味着新资金入场做多,趋势可能持续。
- 空头信号:价格下跌 且 持仓量增加。这通常意味着新资金入场做空,跌势可能持续。
- 平仓信号:持仓量大幅减少(资金离场)或 价格反转。
代码实现:
# -*- coding: utf-8 -*-
from jqdata import *
def initialize(context):
# 初始化设置
set_option('use_real_price', True) # 开启真实价格模式
log.set_level('order', 'error') # 过滤日志
# 设置期货账户
# 注意:期货策略必须将账户类型设置为 'futures'
set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='futures')])
# 设置交易标的:螺纹钢主力
g.underlying_symbol = 'RB'
# 设置基准
set_benchmark('RB9999.XSGE')
# 设定交易费率(示例:万分之1)
set_order_cost(OrderCost(open_commission=0.0001, close_commission=0.0001, close_today_commission=0.0001), type='futures')
# 每天开盘运行
run_daily(trade_logic, 'every_bar')
def trade_logic(context):
# 1. 获取当前主力合约代码
# get_dominant_future 获取主力合约,会自动处理换月
dom_contract = get_dominant_future(g.underlying_symbol)
if not dom_contract:
return
# 2. 获取历史数据:收盘价(close) 和 持仓量(open_interest)
# 获取过去2根K线的数据,用于比较变化
data = attribute_history(dom_contract, 2, '1d', ['close', 'open_interest'])
if len(data) < 2:
return
# 提取当前和前一时刻的数据
curr_close = data['close'][-1]
prev_close = data['close'][-2]
curr_oi = data['open_interest'][-1]
prev_oi = data['open_interest'][-2]
# 3. 计算变化量
price_change = curr_close - prev_close
oi_change = curr_oi - prev_oi
# 获取当前持仓情况
long_positions = context.portfolio.long_positions
short_positions = context.portfolio.short_positions
# 4. 交易逻辑
# --- 开仓逻辑 ---
# 场景A:价格上涨 + 持仓量增加 -> 视为多头强势,开多
if price_change > 0 and oi_change > 0:
# 如果没有多单,则开多
if dom_contract not in long_positions:
# 先平掉可能的空单
if dom_contract in short_positions:
order_target(dom_contract, 0, side='short')
# 开多单 (按价值下单,例如使用当前可用资金的30%)
cash = context.portfolio.available_cash
order_value(dom_contract, cash * 0.3, side='long')
log.info(f"多头信号:价格上涨,持仓增加。买入 {dom_contract}")
# 场景B:价格下跌 + 持仓量增加 -> 视为空头强势,开空
elif price_change < 0 and oi_change > 0:
# 如果没有空单,则开空
if dom_contract not in short_positions:
# 先平掉可能的多单
if dom_contract in long_positions:
order_target(dom_contract, 0, side='long')
# 开空单
cash = context.portfolio.available_cash
order_value(dom_contract, cash * 0.3, side='short')
log.info(f"空头信号:价格下跌,持仓增加。卖出 {dom_contract}")
# --- 平仓/止损逻辑 (简化版) ---
# 场景C:持仓量大幅减少 (资金离场),平掉所有仓位
# 这里定义大幅减少为减少超过 5%
elif oi_change < 0 and abs(oi_change) / prev_oi > 0.05:
if dom_contract in long_positions:
order_target(dom_contract, 0, side='long')
log.info(f"离场信号:持仓量大幅下降。平多 {dom_contract}")
if dom_contract in short_positions:
order_target(dom_contract, 0, side='short')
log.info(f"离场信号:持仓量大幅下降。平空 {dom_contract}")
# 5. 移仓换月处理
# 检查持仓中是否有非主力合约,如果有则平仓
for security in list(context.portfolio.positions.keys()):
if security != dom_contract:
# 平多
if context.portfolio.positions[security].total_amount > 0:
order_target(security, 0, side='long')
log.info(f"移仓换月:平掉旧合约多单 {security}")
# 平空 (注意:JoinQuant中空单仓位在short_positions中)
if security in short_positions and short_positions[security].total_amount > 0:
order_target(security, 0, side='short')
log.info(f"移仓换月:平掉旧合约空单 {security}")
代码关键点解析
-
attribute_history(..., fields=['open_interest']):
这是获取持仓量数据的核心函数。它返回一个 DataFrame,其中包含open_interest列。通过比较data['open_interest'][-1](当前) 和data['open_interest'][-2](上一个时间点),我们可以判断资金是在流入还是流出。 -
get_dominant_future:
期货合约会到期,策略必须始终跟踪主力合约。此函数自动返回当前的主力合约代码(如RB2310.XSGE)。 -
set_subportfolios(..., type='futures'):
在initialize中必须显式设置账户类型为futures,否则默认是股票账户,无法进行期货开空操作。 -
移仓换月:
代码末尾包含了一个简单的循环,用于检查持仓中的合约是否仍是主力合约。如果不是(说明主力已切换),则平掉旧合约。在实际实盘中,移仓逻辑可能需要更精细的控制(如价差控制),但在回测中这样处理通常足够。
Q&A
Q: 为什么在Tick数据中持仓量属性是 position 而不是 open_interest?
A: 这是聚宽API对象定义的差异。在 Tick 对象中,属性名为 position(代表截至当前时刻的持仓量);而在 get_price 或 attribute_history 返回的 DataFrame 或 SecurityUnitData 对象中,字段名为 open_interest。使用时需注意区分。
Q: 如何获取期货的日内Tick级持仓量变化?
A: 可以在 handle_tick(context, tick) 函数中使用。代码如下:
def handle_tick(context, tick):
current_oi = tick.position
# 结合上一笔tick的持仓量进行逻辑判断
注意:Tick级回测需要申请专门的权限,且运行速度较慢。
Q: open_interest 的单位是什么?
A: 单位是“手”。但在聚宽的数据中,请注意不同交易所的计算标准可能略有差异(单边或双边计算),但在策略中比较同一合约的持仓量变化(增减)时,这种差异通常不影响逻辑判断。
Q: 为什么获取不到主力连续合约(如 RB9999.XSGE)的持仓量?
A: 聚宽的 RB9999.XSGE 是虚拟合约,用于行情展示。在交易策略中,不能直接对 9999 结尾的合约下单,也建议不要直接用它来获取持仓量进行交易决策,因为它的持仓量是拼接的,换月时会发生跳变。正确的做法是使用 get_dominant_future 获取具体的物理合约(如 RB2305.XSGE),然后获取该具体合约的持仓量。