3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个非常经典的**期现套利(Arbitrage)**策略需求。
在编写代码之前,我们需要明确套利的核心逻辑:
IF 主力合约)。510300.SH)。以下是一个完整的 Python 策略代码。该策略会实时监控价差,并执行正向套利(买 ETF,空期货)。
注意:为了简化演示,本策略假设你已经持有 ETF 或资金充足,且主要演示正套逻辑(因为国内融券卖出 ETF 难度较大,反套较难实现)。
# -*- coding: gbk -*-
import time
def init(ContextInfo):
# ================= 策略参数设置 =================
# 股票资金账号 (请修改为你的实际账号)
ContextInfo.stock_account = 'YOUR_STOCK_ACCOUNT'
# 期货资金账号 (请修改为你的实际账号)
ContextInfo.future_account = 'YOUR_FUTURE_ACCOUNT'
# 标的设置
ContextInfo.etf_code = '510300.SH' # 沪深300 ETF
ContextInfo.future_code = 'IF00.IF' # IF 主力合约
# 合约乘数 (IF为300, IC为200, IH为300)
ContextInfo.multiplier = 300
# 阈值设置 (百分比)
ContextInfo.open_threshold = 0.015 # 价差超过 1.5% 开仓 (正套)
ContextInfo.close_threshold = 0.002 # 价差回归到 0.2% 平仓
# 交易数量设置
ContextInfo.trade_lot = 1 # 每次交易 1 手期货
# 状态标记
ContextInfo.is_holding = False # 是否持有套利仓位
# 绑定账号 (实盘必须)
ContextInfo.set_account(ContextInfo.stock_account)
ContextInfo.set_account(ContextInfo.future_account)
print("策略初始化完成: 监控 {} 与 {}".format(ContextInfo.future_code, ContextInfo.etf_code))
def handlebar(ContextInfo):
# 只在实时行情的最后一根K线运行,避免历史回测重复触发
if not ContextInfo.is_last_bar():
return
# ================= 获取实时行情 =================
# 获取最新的 Tick 数据
tick_data = ContextInfo.get_full_tick([ContextInfo.etf_code, ContextInfo.future_code])
if ContextInfo.etf_code not in tick_data or ContextInfo.future_code not in tick_data:
return
etf_tick = tick_data[ContextInfo.etf_code]
future_tick = tick_data[ContextInfo.future_code]
# 获取最新价
etf_price = etf_tick['lastPrice']
future_price = future_tick['lastPrice']
# 异常值过滤
if etf_price <= 0 or future_price <= 0:
return
# ================= 计算价差 =================
# 假设 ETF 价格约为指数的 1/1000,将其折算为指数同级价格进行比较
# 注意:不同 ETF 的折算比例不同,510300 通常接近 1/1000
adjusted_etf_price = etf_price * 1000
# 计算价差率 (Premium Rate)
spread_rate = (future_price - adjusted_etf_price) / adjusted_etf_price
# 打印监控日志 (可选,避免日志过多可注释掉)
# print(f"期货: {future_price}, ETF折算: {adjusted_etf_price}, 价差率: {spread_rate:.4%}")
# ================= 仓位查询与状态更新 =================
# 在实盘中,建议通过 get_trade_detail_data 校验真实持仓,这里为了演示使用变量控制
# 如果重启策略,ContextInfo.is_holding 会重置,实盘需增加持仓恢复逻辑
# ================= 交易逻辑 =================
# 1. 开仓逻辑:价差过大,执行正套 (卖期货,买 ETF)
if not ContextInfo.is_holding and spread_rate > ContextInfo.open_threshold:
print(">>> 触发开仓信号: 价差率 {:.2%} > 阈值 {:.2%}".format(spread_rate, ContextInfo.open_threshold))
# 计算需要买入的 ETF 数量
# 1手期货价值 = 期货价格 * 300
# 对应 ETF 股数 = (期货价格 * 300) / ETF价格
# 向下取整到 100 的倍数
hedge_value = future_price * ContextInfo.multiplier
etf_volume = int(hedge_value / etf_price / 100) * 100
# 下单:买入 ETF
print(f"买入 ETF: {ContextInfo.etf_code}, 数量: {etf_volume}")
order_shares(ContextInfo.etf_code, etf_volume, 'fix', etf_price, ContextInfo, ContextInfo.stock_account)
# 下单:卖出开仓 期货
print(f"卖出开仓 期货: {ContextInfo.future_code}, 数量: {ContextInfo.trade_lot}")
sell_open(ContextInfo.future_code, ContextInfo.trade_lot, 'fix', future_price, ContextInfo, ContextInfo.future_account)
ContextInfo.is_holding = True
# 2. 平仓逻辑:价差回归,获利了结 (买平期货,卖 ETF)
elif ContextInfo.is_holding and spread_rate < ContextInfo.close_threshold:
print(">>> 触发平仓信号: 价差率 {:.2%} < 阈值 {:.2%}".format(spread_rate, ContextInfo.close_threshold))
# 获取当前持仓以确定平仓数量 (这里简化为使用开仓时的计算逻辑,实盘应查询实际持仓)
# 查询期货空单持仓
pos_list = get_trade_detail_data(ContextInfo.future_account, 'future', 'position')
future_hold_vol = 0
for pos in pos_list:
if pos.m_strInstrumentID == ContextInfo.future_code and pos.m_nDirection == 49: # 49代表卖方向
future_hold_vol = pos.m_nVolume
# 查询 ETF 持仓
pos_list_stock = get_trade_detail_data(ContextInfo.stock_account, 'stock', 'position')
etf_hold_vol = 0
for pos in pos_list_stock:
if pos.m_strInstrumentID == ContextInfo.etf_code:
etf_hold_vol = pos.m_nVolume
# 执行平仓
if future_hold_vol > 0:
print(f"买入平仓 期货: {ContextInfo.future_code}, 数量: {future_hold_vol}")
# 平仓优先平今 (buy_close_tdayfirst) 或 平昨 (buy_close_ydayfirst),视交易所规则而定
# 这里使用通用买入平仓
buy_close_tdayfirst(ContextInfo.future_code, future_hold_vol, 'fix', future_price, ContextInfo, ContextInfo.future_account)
if etf_hold_vol > 0:
print(f"卖出 ETF: {ContextInfo.etf_code}, 数量: {etf_hold_vol}")
order_shares(ContextInfo.etf_code, -etf_hold_vol, 'fix', etf_price, ContextInfo, ContextInfo.stock_account)
ContextInfo.is_holding = False
数据获取 (get_full_tick):
get_full_tick 获取最新的盘口数据(Tick级),而不是使用 K 线数据。ContextInfo.is_last_bar() 确保代码只在最新的时间点运行,防止在回测加载历史数据时重复下单。价差计算:
adjusted_etf_price = etf_price * 1000。这是因为 510300 ETF 的价格大约是沪深300指数点位的千分之一。如果交易的是 IC(中证500)和 510500,比例也是类似的,但如果是其他品种,请务必确认折算比例。对冲比例计算:
hedge_value = future_price * ContextInfo.multiplier:计算 1 手期货的合约价值。etf_volume = int(hedge_value / etf_price / 100) * 100:计算等市值的 ETF 股数,并向下取整到 100 股(A股最小交易单位)。这是为了保持市值中性。下单函数:
order_shares,正数买入,负数卖出。sell_open(卖出开仓)和 buy_close_tdayfirst(买入平仓)。'fix' (指定价) 配合最新价下单。在实盘高频套利中,为了保证成交,通常会使用 'COMPETE' (对手价) 或者在最新价基础上加减滑点。账号设置:
init 中分别设置,并在下单函数中指定对应的 accId。open_threshold)时,必须覆盖掉双边的手续费和预估滑点,否则会亏损。IF00.IF(主力连续)。在临近交割日时,主力合约会换月,需要注意移仓换月的问题,或者明确指定合约代码(如 IF2312.IF)。ContextInfo.is_holding 变量记录状态。如果软件崩溃重启,这个变量会重置为 False,导致逻辑出错。实盘中应在 init 或 handlebar 开头通过 get_trade_detail_data 查询真实持仓来初始化状态。Q: 如何实现反向套利(期货贴水,买期货卖 ETF)?
A: 逻辑相反:当 spread_rate < -阈值 时,buy_open 期货,order_shares 卖出 ETF。但前提是你必须持有 ETF 底仓,或者拥有融券资格(融券成本通常较高,会压缩套利空间)。
Q: 为什么不用 passorder 函数?
A: passorder 是通用下单函数,功能强大但参数复杂。对于初学者或逻辑清晰的策略,使用封装好的 buy_open, order_shares 等函数可读性更好,不易出错。
Q: 如何提高成交速度?
A: 可以使用 ContextInfo.subscribe_quote 订阅 Tick 数据回调,而不是在 handlebar 里轮询,这样反应速度更快。同时,下单价格可以使用对手价(COMPETE)或超价下单。