🚀 AI 一键生成 joinquant 策略代码

JoinQuant 期货策略:基于持仓量(Open Interest)变化的趋势判断

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

问题描述

怎样获取期货的持仓量数据并用于策略?

解决方案

在聚宽(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. 策略示例:基于“量价配合”的期货趋势策略

策略逻辑:
该策略基于经典的量价分析理论。

  1. 多头信号:价格上涨 且 持仓量增加。这通常意味着新资金入场做多,趋势可能持续。
  2. 空头信号:价格下跌 且 持仓量增加。这通常意味着新资金入场做空,跌势可能持续。
  3. 平仓信号:持仓量大幅减少(资金离场)或 价格反转。

代码实现:

# -*- 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}")

代码关键点解析

  1. attribute_history(..., fields=['open_interest']):
    这是获取持仓量数据的核心函数。它返回一个 DataFrame,其中包含 open_interest 列。通过比较 data['open_interest'][-1] (当前) 和 data['open_interest'][-2] (上一个时间点),我们可以判断资金是在流入还是流出。

  2. get_dominant_future:
    期货合约会到期,策略必须始终跟踪主力合约。此函数自动返回当前的主力合约代码(如 RB2310.XSGE)。

  3. set_subportfolios(..., type='futures'):
    initialize 中必须显式设置账户类型为 futures,否则默认是股票账户,无法进行期货开空操作。

  4. 移仓换月:
    代码末尾包含了一个简单的循环,用于检查持仓中的合约是否仍是主力合约。如果不是(说明主力已切换),则平掉旧合约。在实际实盘中,移仓逻辑可能需要更精细的控制(如价差控制),但在回测中这样处理通常足够。


Q&A

Q: 为什么在Tick数据中持仓量属性是 position 而不是 open_interest
A: 这是聚宽API对象定义的差异。在 Tick 对象中,属性名为 position(代表截至当前时刻的持仓量);而在 get_priceattribute_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),然后获取该具体合约的持仓量。