🚀 AI 一键生成 joinquant 策略代码

JoinQuant 股期混合策略实战:如何利用子账户实现股票多头与股指期货对冲?

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

问题描述

我想写一个股票多头策略,同时用股指期货来对冲市场风险,应该怎么做?

解决方案

这是一个非常经典的Alpha策略(或称为市场中性策略)的雏形。其核心逻辑是:

  1. 多头部分:通过选股策略(如基本面因子、多因子模型)构建股票组合,力求获得超越指数的超额收益(Alpha)。
  2. 空头部分:做空同等价值(或按Beta调整后价值)的股指期货(如IF、IC、IM),以剥离市场波动风险(Beta)。
  3. 收益来源:最终收益 = (股票组合涨跌幅 - 指数涨跌幅) + 期货基差收益 - 交易成本。只要股票组合跑赢指数,无论大盘涨跌,策略理论上都能获利。

下面是一个完整的策略代码示例。

策略逻辑说明

  • 选股范围:沪深300成分股。
  • 选股因子:低市盈率(PE)策略,选取PE最低的20只股票。
  • 对冲标的:沪深300股指期货主力合约(IF)。
  • 资金分配:80%资金用于买股票,20%资金用于期货保证金及抗风险现金。
  • 调仓频率:按月调仓。

策略代码

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

def initialize(context):
    """
    初始化函数,设定基准、手续费、资金分配等
    """
    # 设定基准为沪深300指数
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 设定股票和期货的手续费
    # 股票:买入万三,卖出万三加千一印花税
    set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003, close_today_commission=0, min_commission=5), type='stock')
    # 期货:万分之0.23
    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')
    
    # 设定保证金比例,股指期货一般为10%-15%,这里设为15%
    set_option('futures_margin_rate', 0.15)
    
    # 关键步骤:设置子账户
    # subportfolios[0]: 股票账户,分配80%资金
    # subportfolios[1]: 期货账户,分配20%资金
    total_cash = context.portfolio.starting_cash
    set_subportfolios([
        SubPortfolioConfig(cash=total_cash * 0.8, type='stock'),
        SubPortfolioConfig(cash=total_cash * 0.2, type='index_futures')
    ])
    
    # 定义全局变量
    g.stock_pool = [] # 选出的股票列表
    g.future_code = None # 当前主力合约代码
    g.stock_num = 20 # 持仓股票数量
    
    # 设置定时运行:每月第一个交易日进行调仓
    run_monthly(rebalance, 1, time='09:30')
    # 每天开盘前更新主力合约
    run_daily(update_future_contract, time='09:00')

def update_future_contract(context):
    """
    每天更新当月的主力合约代码
    """
    # 获取沪深300股指期货的主力合约
    dominant = get_dominant_future('IF')
    if dominant:
        g.future_code = dominant

def rebalance(context):
    """
    调仓主函数:先调整股票仓位,再调整期货对冲仓位
    """
    if g.future_code is None:
        log.warn("未获取到主力合约,跳过调仓")
        return
        
    # 1. 选股逻辑
    select_stocks(context)
    
    # 2. 交易股票
    trade_stocks(context)
    
    # 3. 交易期货进行对冲
    hedge_futures(context)

def select_stocks(context):
    """
    选股函数:选取沪深300中PE最低的股票
    """
    # 获取沪深300成分股
    index_stocks = get_index_stocks('000300.XSHG')
    
    # 查询财务数据:市值和PE
    q = query(
        valuation.code,
        valuation.pe_ratio
    ).filter(
        valuation.code.in_(index_stocks),
        valuation.pe_ratio > 0 # 过滤亏损股
    ).order_by(
        valuation.pe_ratio.asc() # 按PE从小到大排序
    ).limit(g.stock_num)
    
    df = get_fundamentals(q)
    g.stock_pool = list(df['code'])
    log.info("今日选股: %s" % g.stock_pool)

def trade_stocks(context):
    """
    股票交易函数
    """
    # 获取股票子账户
    stock_account = context.subportfolios[0]
    
    # 卖出不在股票池中的股票
    for stock in list(stock_account.long_positions.keys()):
        if stock not in g.stock_pool:
            order_target_value(stock, 0, pindex=0)
    
    # 买入股票池中的股票
    # 简单的等权重分配
    if len(g.stock_pool) > 0:
        # 计算每只股票的目标价值 = 股票账户总资产 / 股票数量
        target_value = stock_account.total_value / len(g.stock_pool)
        
        for stock in g.stock_pool:
            order_target_value(stock, target_value, pindex=0)

def hedge_futures(context):
    """
    期货对冲函数
    """
    # 获取股票账户和期货账户
    stock_account = context.subportfolios[0]
    future_account = context.subportfolios[1]
    
    # 1. 计算股票组合的总市值
    stock_value = stock_account.positions_value
    
    # 如果股票市值为0,平掉所有期货空单
    if stock_value == 0:
        for code in future_account.short_positions.keys():
            order_target(code, 0, side='short', pindex=1)
        return

    # 2. 处理主力合约换月
    # 如果持仓中有非当前主力合约的空单,先平仓
    for code in list(future_account.short_positions.keys()):
        if code != g.future_code:
            order_target(code, 0, side='short', pindex=1)
            log.info("平掉旧合约: %s" % code)
            
    # 3. 计算需要对冲的手数
    # 获取期货当前价格
    future_price = get_price(g.future_code, count=1, end_date=context.current_dt, fields=['close'])['close'][0]
    
    # 合约乘数,IF是300元/点
    multiplier = 300 
    
    # 计算合约价值
    contract_value = future_price * multiplier
    
    # 计算需做空的手数 = 股票总市值 / 单张合约价值
    # 这里采用完全对冲(1:1),也可以根据Beta调整
    short_amount = int(stock_value / contract_value)
    
    # 4. 下单调整期货仓位
    # 注意:pindex=1 使用期货子账户
    # side='short' 表示操作空单
    # order_target 调整到目标持仓量
    order_target(g.future_code, short_amount, side='short', pindex=1)
    
    log.info("股票市值: %.2f, 期货价格: %.2f, 对冲手数: %d" % (stock_value, future_price, short_amount))

关键点解析

  1. 子账户设置 (set_subportfolios)

    • 这是实现股期联动的关键。我们将资金拆分为两部分:pindex=0 用于股票,pindex=1 用于期货。
    • 股票账户类型设为 'stock',期货账户类型设为 'index_futures'
    • 通常建议预留 20%-30% 的资金在期货账户,以防止期货价格上涨导致保证金不足被强平。
  2. 主力合约切换 (get_dominant_future)

    • 期货合约会到期,策略必须包含自动换月的逻辑。代码中在 hedge_futures 里检查持仓合约是否等于当前主力合约,如果不等则平掉旧合约,并在后续步骤开新合约。
  3. 对冲数量计算

    • 公式:手数 = 股票总市值 / (期货价格 * 合约乘数)
    • 沪深300 (IF) 乘数是 300,中证500 (IC) 乘数是 200。
    • 本策略采用 1:1 市值对冲。进阶做法是计算股票组合相对于指数的 Beta 值,手数 = (股票市值 * Beta) / 合约价值
  4. 下单函数 (order_target)

    • 期货做空使用的是 side='short'
    • order_target 会自动计算当前持仓和目标持仓的差额进行买卖,非常适合调仓逻辑。

常见问题 Q&A

Q: 为什么我的策略回测时,期货账户经常爆仓?
A: 可能是资金分配问题。如果股票满仓,期货端只留了刚好够开仓的保证金,一旦指数上涨,期货空单会亏损,导致保证金不足。建议在 set_subportfolios 中给期货账户分配更多现金(如20%-30%),或者降低股票仓位。

Q: 如何更换为中证500对冲?
A:

  1. 将基准改为 '000905.XSHG'
  2. 选股范围 get_index_stocks 改为 '000905.XSHG'
  3. 主力合约获取改为 get_dominant_future('IC')
  4. 合约乘数 multiplier 改为 200。

Q: 什么是基差风险?
A: 股指期货价格通常和现货指数价格不一致(通常是贴水,即期货比现货便宜)。如果贴水幅度扩大,即使股票跑赢了指数,总收益也可能因为期货端的额外亏损而减少。这是此类策略的主要风险之一。