问题描述
解决方案
这是一个基于 JoinQuant(聚宽)平台的**股指期货(IF - 沪深300)与现货 ETF(510300 - 华泰柏瑞沪深300ETF)**的期现套利策略。
策略原理
该策略主要捕捉正向套利机会(Cash-and-Carry Arbitrage):
- 开仓条件:当期货价格显著高于现货价格(基差率 > 设定阈值,例如 1%)时,认为期货被高估。此时买入现货 ETF,同时卖空股指期货。
- 平仓条件:当期货价格与现货价格收敛(基差率 < 设定阈值,例如 0% 或负值)时,卖出现货 ETF,同时平仓买入股指期货,赚取基差缩小的利润。
- 对冲比例:1手 IF 合约价值 = 期货价格 × 300。买入等市值的 ETF 进行完全对冲。
策略代码
# -*- coding: utf-8 -*-
from jqdata import *
def initialize(context):
"""
初始化函数
"""
# 1. 设定基准:沪深300
set_benchmark('000300.XSHG')
# 2. 开启真实价格模式(动态复权)
set_option('use_real_price', True)
# 3. 设置手续费
# 股票/ETF:买入万3,卖出万3(ETF免印花税)
set_order_cost(OrderCost(open_tax=0, close_tax=0, 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')
# 4. 设定账户资金分配 (股票账户和期货账户)
# 注意:实盘或回测中需要确保账户有足够的资金支持期货保证金和ETF全额购买
init_cash = context.portfolio.starting_cash
set_subportfolios([SubPortfolioConfig(cash=init_cash, type='stock'),
SubPortfolioConfig(cash=init_cash, type='index_futures')])
# 5. 定义全局变量
g.etf_code = '510300.XSHG' # 现货标的:沪深300ETF
g.future_underlying = 'IF' # 期货标的:沪深300股指期货
g.contract_multiplier = 300 # IF合约乘数
# 6. 套利阈值设置
g.open_threshold = 0.015 # 开仓阈值:期货溢价 1.5%
g.close_threshold = 0.002 # 平仓阈值:溢价收敛至 0.2%
# 记录当前持有的期货合约代码
g.current_future_code = None
# 设置运行频率,这里使用每分钟运行一次
run_daily(arbitrage_logic, time='every_bar')
def arbitrage_logic(context):
"""
套利核心逻辑,每分钟执行
"""
# 1. 获取当前主力合约
dominant_future = get_dominant_future(g.future_underlying)
if not dominant_future:
return
# 如果主力合约发生切换,且持有旧合约,建议先平仓(简化逻辑)
if g.current_future_code and g.current_future_code != dominant_future:
close_all_positions(context)
g.current_future_code = dominant_future
return
g.current_future_code = dominant_future
# 2. 获取价格数据
# 获取ETF最新价
etf_data = get_price(g.etf_code, count=1, end_date=context.current_dt, frequency='1m', fields=['close'])
if etf_data.empty:
return
etf_price = etf_data['close'][-1]
# 获取期货最新价
future_data = get_price(g.current_future_code, count=1, end_date=context.current_dt, frequency='1m', fields=['close'])
if future_data.empty:
return
future_price = future_data['close'][-1]
# 3. 计算基差率 (Spread Rate)
# 基差率 = (期货价格 - 现货价格) / 现货价格
# 注意:这里简单用ETF价格代替现货指数价格,实际操作中ETF可能有折溢价,但ETF是可以直接交易的资产
spread_rate = (future_price - etf_price) / etf_price
# 4. 获取当前持仓状态
# ETF持仓
long_position = context.portfolio.positions[g.etf_code].total_amount
# 期货空单持仓
short_position = context.portfolio.short_positions[g.current_future_code].total_amount
# 5. 交易信号判断
# --- 开仓逻辑 (正向套利:买现货,卖期货) ---
if spread_rate > g.open_threshold and long_position == 0 and short_position == 0:
log.info("触发开仓信号: 基差率 {:.2%}, 期货 {}, 现货 {}".format(spread_rate, future_price, etf_price))
# 计算可买手数
# 1手期货价值
future_value_per_hand = future_price * g.contract_multiplier
# 账户可用资金
cash = context.portfolio.available_cash
# 简易资金管理:预留20%资金作为期货保证金缓冲,剩余资金用于买ETF
# 假设期货保证金15%,ETF全额。
# 1组套利组合需要的资金约 = future_value_per_hand * (1 + 0.15)
cost_per_unit = future_value_per_hand * 1.2
hands = int(cash // cost_per_unit)
if hands > 0:
# 1. 卖空期货
order_target(g.current_future_code, 0, side='long') # 确保无多单
op_future = order(g.current_future_code, hands, side='short')
if op_future:
# 2. 买入对应价值的ETF
# 需买入ETF金额 = 手数 * 期货价格 * 300
target_etf_value = hands * future_value_per_hand
op_etf = order_value(g.etf_code, target_etf_value)
if op_etf:
log.info("开仓成功: 卖空 {} 手期货, 买入 {:.2f} 元 ETF".format(hands, target_etf_value))
else:
log.warn("ETF买入失败,尝试平仓期货以防裸空")
order_target(g.current_future_code, 0, side='short')
# --- 平仓逻辑 (基差收敛:卖现货,平期货) ---
elif spread_rate < g.close_threshold and long_position > 0 and short_position > 0:
log.info("触发平仓信号: 基差率 {:.2%}".format(spread_rate))
# 1. 平仓期货空单
order_target(g.current_future_code, 0, side='short')
# 2. 卖出ETF
order_target(g.etf_code, 0)
log.info("平仓完成")
# 记录数据用于画图
record(Spread_Rate=spread_rate)
def close_all_positions(context):
"""
清空所有仓位
"""
# 清空股票/ETF
for stock in context.portfolio.positions:
order_target(stock, 0)
# 清空期货空单
for future in context.portfolio.short_positions:
order_target(future, 0, side='short')
# 清空期货多单(如果有)
for future in context.portfolio.long_positions:
order_target(future, 0, side='long')
策略关键点说明
-
标的选择:
- 期货:使用
get_dominant_future('IF')获取沪深300股指期货的主力合约。主力合约流动性最好,适合交易。 - 现货:使用
510300.XSHG(华泰柏瑞沪深300ETF)。这是市场上流动性最好的300ETF之一,与指数拟合度高。
- 期货:使用
-
基差率计算:
- 公式:
(期货价格 - ETF价格) / ETF价格。 - 当比率大于
g.open_threshold(如1.5%)时,说明期货相对于现货太贵了,存在无风险(或低风险)套利空间。
- 公式:
-
资金配比与对冲:
- 股指期货合约乘数为 300。
- 为了实现市值中性(Delta Neutral),买入 ETF 的总市值必须等于
期货手数 * 期货价格 * 300。 - 代码中简单的资金管理逻辑是预留部分现金作为期货保证金(防止爆仓),其余资金用于购买 ETF。
-
交易执行:
- 先开空单期货,成功后再买入 ETF。这是为了防止 ETF 买入后期货无法开仓导致单边风险暴露。
- 平仓时同时平掉双边头寸。
-
注意事项:
- T+1 限制:A股市场的 ETF 通常是 T+1 交易(部分跨境或债券ETF除外,但510300是T+1)。这意味着当天买入的 ETF 当天不能卖出。如果基差在当天迅速收敛,策略可能无法立即平仓。
- 合约换月:代码中包含简单的换月逻辑(当主力合约变更时平仓)。在实际生产中,通常会进行“移仓换月”(Roll Over),即平掉旧合约同时开新合约。
- 滑点与冲击成本:实际交易中需要考虑买卖价差和市场深度。
Q&A
Q: 为什么选择 ETF 而不是一篮子股票?
A: 购买沪深300的一篮子股票(300只股票)资金门槛极高且交易成本(佣金、印花税)复杂,容易产生跟踪误差。ETF 是现成的组合,交易便捷,且免收印花税,是期现套利最常用的现货工具。
Q: 什么是“负基差”套利?代码里为什么没有写?
A: 负基差套利(反向套利)需要“买入期货,卖空现货”。在A股市场,融券卖空 ETF 难度较大(券源少、成本高),因此个人投资者或小型机构通常只做正向套利(买现货卖期货)。
Q: 如何优化这个策略?
A: 1. 优化阈值:根据历史波动率动态调整开平仓阈值(如 Bollinger Bands)。2. 换月优化:在合约交割周自动移仓,而不是简单平仓。3. 资金利用率:利用 ETF 作为融资融券的担保品,进一步提高资金杠杆(需谨慎风控)。