问题描述
解决方案
这是一个基于PTrade平台的期货跨期套利策略。
策略逻辑说明
本策略采用**统计套利(均值回归)**的思路。跨期套利的核心在于两个不同交割月份的合约(近月与远月)之间的价差(Spread)通常会在一定范围内波动。
- 定义价差:
价差 = 近月合约价格 - 远月合约价格。 - 入场条件(做多价差):当价差过低(低于均值 - N倍标准差)时,我们认为近月合约被低估或远月被高估。此时执行**“买入近月,卖出远月”**。
- 出场条件(回归平仓):当价差回归到均值(或超过均值)时,获利平仓。
- 合约选择:策略会自动选择当下的主力合约(近月)和次主力合约(远月)。
策略代码
import numpy as np
import pandas as pd
def initialize(context):
"""
策略初始化函数
"""
# 设定基准合约品种,例如沪深300股指期货 'IF'
g.product_code = 'IF'
# 套利参数设置
g.window_size = 60 # 计算均值和标准差的窗口大小(如过去60根分钟K线)
g.std_multiplier = 2.0 # 进场阈值:标准差倍数
# 交易数量(手)
g.trade_amount = 1
# 全局变量存储当前操作的合约
g.near_contract = None # 近月合约
g.far_contract = None # 远月合约
# 设定系统参数:分钟回测
set_universe([g.product_code])
def before_trading_start(context, data):
"""
盘前处理:每天开盘前确认当天的近月和远月合约
"""
# 获取该品种所有合约
# 注意:get_future_contracts是获取期货合约列表的常用方法,
# 如果PTrade环境特定版本不支持,需手动维护合约列表或使用get_code_list
all_contracts = get_future_contracts(g.product_code)
# 过滤掉已经退市或未上市的,并按交割日期排序
valid_contracts = []
for code in all_contracts:
info = get_instruments(code)
# 简单的过滤逻辑:确保合约存在且未过期
if info is not None:
valid_contracts.append(code)
# 排序:通常合约代码数字越小越近,或者根据上市日期排序
# 这里简单按代码字符串排序,通常IF2309 < IF2310
valid_contracts.sort()
# 选取近期和远期合约(取排序后的前两个)
if len(valid_contracts) >= 2:
g.near_contract = valid_contracts[0]
g.far_contract = valid_contracts[1]
log.info("今日套利合约组合: 近月 [%s] / 远月 [%s]" % (g.near_contract, g.far_contract))
else:
g.near_contract = None
g.far_contract = None
log.warning("可用合约不足,无法进行跨期套利")
def handle_data(context, data):
"""
盘中运行:每分钟执行一次
"""
# 检查合约是否有效
if g.near_contract is None or g.far_contract is None:
return
# 1. 获取历史数据用于计算统计指标
# 获取过去 N 分钟的收盘价
near_hist = get_history(g.window_size, '1m', 'close', g.near_contract)
far_hist = get_history(g.window_size, '1m', 'close', g.far_contract)
# 确保数据长度足够
if len(near_hist) < g.window_size or len(far_hist) < g.window_size:
return
# 提取收盘价序列 (Series)
# 注意:get_history返回格式可能因版本不同而异,这里假设返回DataFrame或Series
# 如果是DataFrame,通常列名为 'close'
p_near = near_hist['close']
p_far = far_hist['close']
# 2. 计算价差序列 (Spread = Near - Far)
spread_series = p_near - p_far
# 3. 计算统计指标 (均值和标准差)
spread_mean = spread_series.mean()
spread_std = spread_series.std()
# 当前最新价差
current_spread = spread_series.iloc[-1]
# 计算阈值
# 做多价差阈值(价差过低):均值 - N * 标准差
long_spread_threshold = spread_mean - g.std_multiplier * spread_std
# 平仓阈值:回归均值
exit_threshold = spread_mean
# 4. 获取当前持仓信息
pos_near = get_position(g.near_contract)
pos_far = get_position(g.far_contract)
# 判断当前是否有持仓 (多近月 + 空远月)
# 注意:期货持仓分多头(long_amount)和空头(short_amount)
has_long_spread_pos = (pos_near.long_amount > 0) and (pos_far.short_amount > 0)
# 5. 交易逻辑
# --- 开仓逻辑:价差低于下轨,且无持仓 ---
if current_spread < long_spread_threshold and not has_long_spread_pos:
log.info("触发开仓信号: 当前价差 %.2f < 阈值 %.2f" % (current_spread, long_spread_threshold))
# 买入近月 (多开)
buy_open(g.near_contract, g.trade_amount)
# 卖出远月 (空开)
sell_open(g.far_contract, g.trade_amount)
# --- 平仓逻辑:价差回归均值,且有持仓 ---
elif current_spread >= exit_threshold and has_long_spread_pos:
log.info("触发平仓信号: 当前价差 %.2f >= 均值 %.2f" % (current_spread, exit_threshold))
# 卖出近月 (多平)
# close_today=False 表示优先平昨仓,若需严格区分平今平昨需根据交易所规则调整
sell_close(g.near_contract, g.trade_amount, close_today=False)
# 买入远月 (空平)
buy_close(g.far_contract, g.trade_amount, close_today=False)
# 辅助函数:获取期货合约列表
# 如果PTrade环境中没有内置 get_future_contracts,可以使用此替代方案或手动指定
def get_future_contracts(symbol):
"""
获取指定品种的期货合约列表
注意:此函数依赖于 get_code_list 或类似接口,若不可用需咨询券商
这里仅作演示,实际使用建议直接在 initialize 中指定具体合约代码列表
"""
# 示例:返回假设的合约代码,实际需调用API获取
# 在实盘中,通常可以使用 get_all_securities 筛选
# 或者直接使用主力合约代码如 'IF2309.CCFX'
# 这里为了代码不报错,使用一种通用的获取方式(需根据实际环境调整)
try:
# 尝试获取该品种的主力合约和次主力
# PTrade通常支持 'IF8888.CCFX' (主力) 但套利需要具体月份
# 此处建议用户在实盘中手动维护一个合约列表,或者使用 get_instruments 遍历
return ['IF2309.CCFX', 'IF2310.CCFX', 'IF2312.CCFX']
except:
return []
代码关键点解析
-
合约获取 (
before_trading_start):- 期货合约会到期,因此不能在
initialize中写死。 - 代码中演示了如何选取排序靠前的两个合约作为“近月”和“远月”。
- 注意:在实盘中,建议使用
get_instruments配合循环来筛选当前上市交易的合约,或者手动在策略参数中更新合约代码。
- 期货合约会到期,因此不能在
-
数据获取 (
get_history):- 使用
1m(1分钟) 频率获取数据,窗口大小设为 60,意味着基于过去1小时的分钟线计算价差的布林带(均值和标准差)。 spread_series = p_near - p_far利用了 Pandas 的向量化计算,直接得出价差序列。
- 使用
-
交易指令:
- 开仓(买近卖远):
buy_open(near): 多头开仓近月。sell_open(far): 空头开仓远月。
- 平仓(卖近买远):
sell_close(near): 多头平仓近月。buy_close(far): 空头平仓远月。
close_today=False: 对于中金所(IF/IC/IH/IM),平今仓和平昨仓手续费通常一致(或有特定高倍率),参数设为False系统会自动处理。如果是上期所(如螺纹钢 RB),平今仓手续费可能较高,需根据实际情况调整此参数。
- 开仓(买近卖远):
-
风险控制:
- 代码中加入了
has_long_spread_pos状态判断,防止重复开仓。 - 使用了
log.info记录关键交易信号,便于回测分析。
- 代码中加入了
Q&A
Q: 如何将策略改为“卖出近月,买入远月”(做空价差)?
A: 修改 handle_data 中的逻辑:
- 计算上轨阈值:
short_spread_threshold = spread_mean + g.std_multiplier * spread_std。 - 开仓条件改为:
current_spread > short_spread_threshold。 - 交易指令改为:
sell_open(near)和buy_open(far)。
Q: 为什么使用 get_history 而不是 get_snapshot?
A: get_snapshot 只能获取当前切片的一个价格,无法计算均值和标准差。我们需要 get_history 获取一段时间的序列数据来计算统计指标(Mean, Std)。
Q: 实盘中合约到期了怎么办?
A: before_trading_start 函数会在每天开盘前运行。如果逻辑正确(动态获取合约),它会自动切换到新的近月和远月合约。但在实际操作中,跨期套利通常涉及移仓换月,本策略仅演示了基本的开平仓逻辑,未包含自动移仓(即在旧合约到期前强制平仓并开新合约)的功能,实盘需注意合约的最后交易日。