3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在配对交易(Pair Trading)中,通过Engle-Granger (EG) 两步法协整检验可以验证两只历史走势高度相关的 ETF 之间是否存在长期稳定的均衡关系。当两者对数价格的线性组合(残差)偏离均值时,通常存在均值回归机会。
本文将详细介绍如何在 QMT (迅投量化交易平台) 中基于 Python API 实现一个完整的协整配对套利策略。
510300.SH 与 510500.SH 或同板块行业 ETF)。#encoding:gbk
import numpy as np
import pandas as pd
import statsmodels.api as sm
def init(ContextInfo):
# 设定套利标的对 (Y, X)
ContextInfo.etf_Y = '510300.SH' # 300ETF
ContextInfo.etf_X = '510500.SH' # 500ETF
ContextInfo.set_universe([ContextInfo.etf_Y, ContextInfo.etf_X])
# 回测与计算参数
ContextInfo.window = 60 # 协整检验滑动窗口
ContextInfo.entry_z = 1.5 # 开仓阈值
ContextInfo.exit_z = 0.2 # 平仓阈值
ContextInfo.position = 0 # 0: 无仓位, 1: 多Y空X, -1: 空Y多X
# 设置交易账号
ContextInfo.account = '6000000001'
ContextInfo.set_account(ContextInfo.account)
print("【初始化完成】统计套利策略启动")
def handlebar(ContextInfo):
# 仅在最新 Bar 判定信号
if not ContextInfo.is_last_bar():
return
# 获取历史收盘价数据
data = ContextInfo.get_market_data_ex(
fields=['close'],
stock_code=[ContextInfo.etf_Y, ContextInfo.etf_X],
period='1d',
count=ContextInfo.window + 1
)
df_Y = data[ContextInfo.etf_Y]['close']
df_X = data[ContextInfo.etf_X]['close']
if len(df_Y) < ContextInfo.window:
return
# 对价格取对数
log_Y = np.log(df_Y)
log_X = np.log(df_X)
# 1. 线性回归计算协整对数系数 (OLS)
X_with_const = sm.add_constant(log_X)
model = sm.OLS(log_Y, X_with_const).fit()
alpha = model.params['const']
beta = model.params['close']
# 2. 计算残差与 Z-Score
spreads = log_Y - (beta * log_X + alpha)
mean_spread = np.mean(spreads)
std_spread = np.std(spreads)
if std_spread == 0:
return
current_spread = spreads.iloc[-1]
z_score = (current_spread - mean_spread) / std_spread
print(f"[BarPos: {ContextInfo.barpos}] Beta: {beta:.4f}, Current Z-Score: {z_score:.4f}")
# 3. 交易逻辑判断与执行
# 平仓逻辑
if ContextInfo.position != 0 and abs(z_score) < ContextInfo.exit_z:
print("===> Z-Score 回归均值,执行平仓")
order_target_percent(ContextInfo.etf_Y, 0, ContextInfo, ContextInfo.account)
order_target_percent(ContextInfo.etf_X, 0, ContextInfo, ContextInfo.account)
ContextInfo.position = 0
# 开仓逻辑 1: Spread 过高 (买 X 卖 Y)
elif ContextInfo.position == 0 and z_score > ContextInfo.entry_z:
print("===> Z-Score 突破上限,做空 Y 做多 X")
passorder(24, 1113, ContextInfo.account, ContextInfo.etf_Y, 5, -1, 0.4, ContextInfo) # 卖出 Y
passorder(23, 1113, ContextInfo.account, ContextInfo.etf_X, 5, -1, 0.4, ContextInfo) # 买入 X
ContextInfo.position = -1
# 开仓逻辑 2: Spread 过低 (买 Y 卖 X)
elif ContextInfo.position == 0 and z_score < -ContextInfo.entry_z:
print("===> Z-Score 突破下限,做多 Y 做空 X")
passorder(23, 1113, ContextInfo.account, ContextInfo.etf_Y, 5, -1, 0.4, ContextInfo) # 买入 Y
passorder(24, 1113, ContextInfo.account, ContextInfo.etf_X, 5, -1, 0.4, ContextInfo) # 卖出 X
ContextInfo.position = 1
statsmodels.tsa.stattools.adfuller 单位根检验,只有通过检验(p-value < 0.05)时才触发开仓,避免伪回归。passorder 中的 28: 融券卖出),或仅做单向多头替代。get_market_data_ex 获取历史行情,并结合 passorder 进行比例下单,可有效保证高执行效率。