问题描述
解决方案
这是一个基于 PTrade 平台的股指期货跨期套利(Calendar Spread Arbitrage)策略范例。
策略原理
该策略基于价差的均值回归原理(统计套利):
- 选取标的:选取同一品种的两个不同到期日的合约(例如:近月合约
IF2309和远月合约IF2312)。 - 计算价差:
价差 = 近月合约价格 - 远月合约价格。 - 统计指标:利用历史数据计算价差的均值(Mean)和标准差(Std)。
- 交易信号:
- 正向套利(做多价差):当
价差 < 均值 - k * 标准差时,认为价差过小,买入近月、卖出远月。 - 反向套利(做空价差):当
价差 > 均值 + k * 标准差时,认为价差过大,卖出近月、买入远月。 - 平仓:当价差回归到均值附近时,平掉双边仓位。
- 正向套利(做多价差):当
PTrade 策略代码
import numpy as np
import pandas as pd
def initialize(context):
"""
策略初始化函数
"""
# 1. 设定套利合约 (请根据实际时间修改合约代码)
# 近月合约 (Near Leg)
g.near_contract = 'IF2309.CCFX'
# 远月合约 (Far Leg)
g.far_contract = 'IF2312.CCFX'
# 2. 设置股票池
g.security = [g.near_contract, g.far_contract]
set_universe(g.security)
# 3. 策略参数设置
g.window_size = 60 # 计算均值和标准差的历史窗口长度 (如60个周期)
g.k_std = 2.0 # 开仓阈值:标准差的倍数
g.lot_size = 1 # 单次交易手数
# 4. 设置手续费和保证金 (可选,根据实际情况调整)
# 设置期货保证金比例,例如 12%
set_margin_rate('IF', 0.12)
# 设置手续费,例如万分之0.23
set_future_commission('IF', 0.000023)
# 5. 初始化信号状态
# 0: 空仓, 1: 持有正向套利仓位 (多近空远), -1: 持有反向套利仓位 (空近多远)
g.status = 0
def handle_data(context, data):
"""
盘中运行函数,假设运行频率为分钟级
"""
# 检查数据是否完整,如果任一合约停牌或无数据,则跳过
if g.near_contract not in data or g.far_contract not in data:
return
# 1. 获取历史数据用于计算统计指标
# 获取过去 g.window_size 个周期的收盘价
hist_near = get_history(g.window_size, '1m', 'close', g.near_contract, fq=None, include=False)
hist_far = get_history(g.window_size, '1m', 'close', g.far_contract, fq=None, include=False)
# 确保数据长度足够
if len(hist_near) < g.window_size or len(hist_far) < g.window_size:
log.info("历史数据不足,跳过计算")
return
# 提取收盘价序列 (注意:get_history返回DataFrame,需提取values)
# 这里假设返回的是单只股票的DF,列名为close
close_near_arr = hist_near['close'].values
close_far_arr = hist_far['close'].values
# 2. 计算价差序列 (Spread = Near - Far)
spread_history = close_near_arr - close_far_arr
# 计算均值和标准差
spread_mean = np.mean(spread_history)
spread_std = np.std(spread_history)
# 3. 获取当前实时价格
current_near_price = data[g.near_contract]['close']
current_far_price = data[g.far_contract]['close']
current_spread = current_near_price - current_far_price
# 定义上下轨
upper_band = spread_mean + g.k_std * spread_std
lower_band = spread_mean - g.k_std * spread_std
# 4. 交易逻辑
# --- 开仓逻辑 ---
if g.status == 0:
# 情况A: 价差过大 -> 卖近买远 (做空价差)
if current_spread > upper_band:
log.info("触发反向套利开仓: 价差 %.2f > 上轨 %.2f" % (current_spread, upper_band))
# 卖出开仓近月
sell_open(g.near_contract, g.lot_size)
# 买入开仓远月
buy_open(g.far_contract, g.lot_size)
g.status = -1
# 情况B: 价差过小 -> 买近卖远 (做多价差)
elif current_spread < lower_band:
log.info("触发正向套利开仓: 价差 %.2f < 下轨 %.2f" % (current_spread, lower_band))
# 买入开仓近月
buy_open(g.near_contract, g.lot_size)
# 卖出开仓远月
sell_open(g.far_contract, g.lot_size)
g.status = 1
# --- 平仓逻辑 ---
elif g.status == -1: # 当前持有反向套利仓位 (空近多远)
# 当价差回归到均值以下,平仓获利
if current_spread <= spread_mean:
log.info("触发反向套利平仓: 价差 %.2f 回归均值 %.2f" % (current_spread, spread_mean))
# 买入平仓近月
buy_close(g.near_contract, g.lot_size)
# 卖出平仓远月
sell_close(g.far_contract, g.lot_size)
g.status = 0
elif g.status == 1: # 当前持有正向套利仓位 (多近空远)
# 当价差回归到均值以上,平仓获利
if current_spread >= spread_mean:
log.info("触发正向套利平仓: 价差 %.2f 回归均值 %.2f" % (current_spread, spread_mean))
# 卖出平仓近月
sell_close(g.near_contract, g.lot_size)
# 买入平仓远月
buy_close(g.far_contract, g.lot_size)
g.status = 0
def after_trading_end(context, data):
"""
盘后处理
"""
log.info("今日交易结束,当前状态: %s" % g.status)
代码关键点解析
-
合约代码 (
g.near_contract,g.far_contract):- 期货合约有到期日,代码会随时间变化(如
IF2309代表2023年9月交割)。在实盘中,您可能需要编写逻辑自动获取主力合约和次主力合约,但在本范例中为了清晰展示逻辑,采用了硬编码。 - 后缀
.CCFX代表中金所。
- 期货合约有到期日,代码会随时间变化(如
-
数据获取 (
get_history):- 我们获取了过去
g.window_size个周期的分钟线数据来计算布林带(均值和标准差)。 include=False表示不包含当前这根未走完的K线,防止信号闪烁。
- 我们获取了过去
-
下单指令:
buy_open: 买入开仓(做多)。sell_open: 卖出开仓(做空)。buy_close: 买入平仓(平空头)。sell_close: 卖出平仓(平多头)。- 注意:PTrade 中没有原生的“组合下单”指令,因此我们是连续发送两个单子。在极端行情下,可能会出现单边成交的风险(Legging Risk),实盘中通常需要更复杂的算法来处理滑点和未成交部分。
-
状态管理 (
g.status):- 使用一个简单的状态变量来防止重复开仓,并确保平仓逻辑与持仓方向匹配。
常见问题 (Q&A)
Q: 如何在回测中设置分钟级别?
A: 在 PTrade 回测界面的“基本设置”中,将“频率”设置为“分钟”,这样 handle_data 就会每分钟执行一次。
Q: 为什么使用 get_history 而不是直接用 data 计算均值?
A: data 对象只包含当前时刻(本分钟)的数据快照。要计算均值和标准差,必须使用 get_history 获取一段时间的历史序列。
Q: 实盘中如何自动换月?
A: 您可以在 before_trading_start 中调用 get_future_contracts (如果券商支持) 或通过 get_instruments 结合成交量判断主力合约,动态更新 g.near_contract 和 g.far_contract。
Q: 这个策略的风险在哪里?
A:
- 单边风险:如果价差持续扩大不回归(例如出现逼空行情),策略会产生亏损。通常需要设置止损线。
- 滑点风险:两条腿下单有时间差,可能导致成交价格偏离预期。
- 流动性风险:远月合约可能流动性较差,导致买卖价差过大。