🚀 AI 一键生成 joinquant 策略代码

JoinQuant 教程:如何构建沪深300股指期货的期现套利策略?

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

问题描述

我想做一个股指期货和现货之间的套利策略,该怎么写?

解决方案

这是一个非常经典的**期现套利(Arbitrage)**策略需求。

策略原理

期现套利主要利用股指期货(如 IF 沪深300期货)与现货指数(如沪深300指数或对应的 ETF)之间的价差(基差)进行获利。

  1. 正向套利(开仓):当期货价格 远高于 现货价格(升水),且价差超过了资金成本和交易成本时:
    • 买入 现货(如沪深300 ETF)。
    • 做空 等市值的股指期货。
  2. 平仓:当期货价格与现货价格 收敛(价差缩小或接近0)时:
    • 卖出 现货。
    • 平仓 期货空单。
  3. 获利来源:赚取基差回归的收益。

代码实现要点

在聚宽(JoinQuant)中实现该策略,需要注意以下几点:

  1. 多账户设置:必须使用 set_subportfolios 同时开启股票账户(买ETF)和期货账户(做空期货)。
  2. 合约匹配:需要计算一手期货合约对应的现货价值(合约乘数通常为300)。
  3. 主力合约:通常使用 get_dominant_future 获取主力合约,或者指定当月合约。

下面是一个完整的、可直接运行的期现套利策略代码。

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

def initialize(context):
    """
    初始化函数
    """
    # 1. 设定基准
    set_benchmark('000300.XSHG')
    
    # 2. 开启真实价格模式(动态复权)
    set_option('use_real_price', True)
    
    # 3. 设置日志级别
    log.set_level('order', 'error')
    
    # 4. 设定多账户:仓位0为股票(ETF),仓位1为期货
    # 假设初始资金 100万,平分给两个账户(实际操作中需根据保证金比例调整)
    init_cash = context.portfolio.starting_cash
    set_subportfolios([
        SubPortfolioConfig(cash=init_cash * 0.8, type='stock'),   # 股票账户买ETF,资金占用大
        SubPortfolioConfig(cash=init_cash * 0.2, type='futures')  # 期货账户做空,保证金交易
    ])
    
    # 5. 设置手续费(这里设置一个简化的费率,实盘请按券商设置)
    # 股票费率
    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='index_futures')
    
    # 6. 定义全局变量
    g.etf_code = '510300.XSHG'      # 现货标的:沪深300ETF
    g.index_code = '000300.XSHG'    # 参考指数:沪深300指数
    g.future_underlying = 'IF'      # 期货品种:IF
    
    # 7. 套利阈值设置
    # 当 (期货 - 现货) / 现货 > open_threshold 时,开仓(正向套利)
    g.open_threshold = 0.02  # 2% 的升水才开仓(示例值,实际需计算资金成本)
    # 当 (期货 - 现货) / 现货 < close_threshold 时,平仓
    g.close_threshold = 0.005 # 0.5% 时平仓
    
    # 运行频率:按分钟回测,捕捉日内价差
    run_daily(arbitrage_logic, time='every_bar')

def arbitrage_logic(context):
    """
    核心套利逻辑,每分钟运行一次
    """
    # 获取当前时间
    current_date = context.current_dt.date()
    
    # 1. 获取主力合约代码
    future_code = get_dominant_future(g.future_underlying)
    if not future_code:
        return

    # 2. 获取价格数据
    # 获取指数现货价格(作为基准计算价差)
    spot_data = attribute_history(g.index_code, 1, '1m', ['close'])
    if len(spot_data) == 0: return
    spot_price = spot_data['close'][-1]
    
    # 获取期货价格
    future_data = attribute_history(future_code, 1, '1m', ['close'])
    if len(future_data) == 0: return
    future_price = future_data['close'][-1]
    
    # 获取ETF价格(实际交易用)
    etf_data = attribute_history(g.etf_code, 1, '1m', ['close'])
    if len(etf_data) == 0: return
    etf_price = etf_data['close'][-1]

    # 3. 计算基差率 (Spread Rate)
    # 基差率 = (期货价格 - 现货指数价格) / 现货指数价格
    spread_rate = (future_price - spot_price) / spot_price
    
    # 4. 获取当前持仓状态
    # pindex=0 是股票账户,pindex=1 是期货账户
    # 检查期货账户是否有空单
    short_positions = context.subportfolios[1].short_positions
    has_short_pos = future_code in short_positions and short_positions[future_code].total_amount > 0
    
    # 5. 交易逻辑
    
    # --- 开仓逻辑 (正向套利) ---
    # 如果升水超过阈值,且当前没有持仓
    if spread_rate > g.open_threshold and not has_short_pos:
        log.info("触发正向套利开仓: 期货价格 %.2f, 指数价格 %.2f, 基差率 %.2f%%" % (future_price, spot_price, spread_rate * 100))
        
        # 计算可买手数
        # 沪深300期货合约乘数为 300
        contract_multiplier = 300
        
        # 计算一手期货对应的市值
        one_future_value = future_price * contract_multiplier
        
        # 获取期货账户可用资金
        future_cash = context.subportfolios[1].available_cash
        # 获取股票账户可用资金
        stock_cash = context.subportfolios[0].available_cash
        
        # 简单资金管理:以期货账户能开的空单数量为基准,或者以股票账户能买的ETF为基准,取小值
        # 假设期货保证金 15%
        margin_rate = 0.15
        max_future_hands = int(future_cash / (one_future_value * margin_rate))
        max_etf_hands_equivalent = int(stock_cash / one_future_value)
        
        # 决定开仓手数
        hands_to_open = min(max_future_hands, max_etf_hands_equivalent)
        
        if hands_to_open > 0:
            # 1. 股票账户买入对应价值的 ETF
            # 需要买入的 ETF 市值 ≈ 期货市值
            target_etf_value = hands_to_open * one_future_value
            order_target_value(g.etf_code, target_etf_value, pindex=0)
            
            # 2. 期货账户卖出开空
            order(future_code, hands_to_open, side='short', pindex=1)
            
            log.info("开仓完成: 买入ETF市值 %.2f, 做空期货 %d 手" % (target_etf_value, hands_to_open))

    # --- 平仓逻辑 ---
    # 如果价差回归(小于平仓阈值),且持有空单
    elif spread_rate < g.close_threshold and has_short_pos:
        log.info("触发套利平仓: 期货价格 %.2f, 指数价格 %.2f, 基差率 %.2f%%" % (future_price, spot_price, spread_rate * 100))
        
        # 1. 卖出所有 ETF (pindex=0)
        order_target(g.etf_code, 0, pindex=0)
        
        # 2. 平掉所有期货空单 (pindex=1)
        # 注意:如果主力合约发生切换,这里需要遍历持仓平掉旧合约,为简化代码,这里只平当前主力
        # 严谨做法是遍历 short_positions.keys() 进行平仓
        for code in short_positions.keys():
            order_target(code, 0, side='short', pindex=1)
            
        log.info("平仓完成")

    # --- 移仓换月逻辑 (可选,简化版) ---
    # 实际交易中,如果持有的是非主力合约,且主力合约已切换,需要进行移仓
    # 本策略简单处理:如果持有的合约不是当前主力,则强制平仓(下次开仓会自动开在主力上)
    if has_short_pos:
        for code in list(short_positions.keys()):
            if code != future_code:
                log.info("主力合约切换,平掉旧合约: %s" % code)
                order_target(code, 0, side='short', pindex=1)
                # 对应的ETF也应该按比例减仓,或者等待下次信号重新平衡
                # 为简化,这里选择全部平仓重来
                order_target(g.etf_code, 0, pindex=0)

策略代码详解

  1. set_subportfolios (关键)

    • 这是实现期现套利的核心。我们创建了两个子账户:type='stock' 用于买卖 ETF,type='futures' 用于交易股指期货。
    • 资金分配比例设为 8:2,因为股票是全额交易,期货是保证金交易(通常10%-15%),所以大部分资金需要放在股票账户。
  2. get_dominant_future:

    • 自动获取当前的 IF(沪深300)主力合约。这避免了手动指定合约代码过期的麻烦。
  3. 基差计算:

    • spread_rate = (future_price - spot_price) / spot_price
    • 这里使用 000300.XSHG (指数本身) 来计算基差,因为 ETF 可能会有微小的跟踪误差,但套利的理论依据是指数。
  4. 对冲比例:

    • one_future_value = future_price * 300
    • 一手 IF 期货代表 300 倍指数点位的价值。买入 ETF 时,必须买入等值的金额,才能实现完全对冲(Delta Neutral)。
  5. 交易指令:

    • ETF 交易使用 pindex=0
    • 期货交易使用 pindex=1,且开空单需指定 side='short'

常见问题 Q&A

Q: 为什么不使用反向套利(买期货,融券卖现货)?
A: 在 A 股市场,融券卖出 ETF 或一篮子股票的成本较高且券源不稳定。对于普通量化策略,正向套利(买现货抛期货)是最可行的方案。

Q: 这里的阈值 0.02 (2%) 是怎么定的?
A: 这是一个示例值。实际实盘中,你需要计算资金成本(无风险利率)、交易手续费、冲击成本等。只有当价差收益覆盖了这些成本后,套利才有意义。

Q: 如何处理期货交割日?
A: 代码中包含了一个简单的移仓逻辑:如果持仓的合约不再是主力合约,则平仓。在实际策略中,通常会在交割日前几天强制平仓或移仓到下月合约,以避免进入交割流程。

Q: 为什么回测时没有交易?
A: 沪深300期货在成熟市场中,升水(期货>现货)幅度通常不大,甚至长期贴水(期货<现货)。如果回测时间段内没有出现超过 2% 的升水,策略就不会开仓。你可以尝试调低 g.open_threshold 进行测试。