🚀 AI 一键生成 qmt 策略代码
立即体验

如何在 QMT 中实现基于 EG 协整检验的行业 ETF 配对套利策略(附 Python 代码)

qmt | 发布时间: 2026/8/1
以下内容由 EasyQuant 生成。

问题描述

请根据以下 SEO 主题生成完整页面内容:

Title: QMT 统计套利:基于协整检验的行业 ETF 配对交易策略

Question: 请帮我编写一个基于 EG 协整检验的 ETF 配对套利 QMT 策略

解决方案

QMT 统计套利:基于协整检验的行业 ETF 配对交易策略

在配对交易(Pair Trading)中,通过Engle-Granger (EG) 两步法协整检验可以验证两只历史走势高度相关的 ETF 之间是否存在长期稳定的均衡关系。当两者对数价格的线性组合(残差)偏离均值时,通常存在均值回归机会。

本文将详细介绍如何在 QMT (迅投量化交易平台) 中基于 Python API 实现一个完整的协整配对套利策略。


1. 策略核心逻辑

  1. 标的选择:选择具有相同行业属性的 ETF 对(例如:510300.SH510500.SH 或同板块行业 ETF)。
  2. 协整回归:在历史窗口(如 60 根日 Bar)内对两只 ETF 的对数价格做线性回归 $Y = \beta X + \alpha$,计算配对残差 $Spread = Y - \beta X - \alpha$。
  3. 信号计算:计算残差的 Z-Score(标准化得分):
    $$Z = \frac{Spread - \mu}{\sigma}$$
  4. 交易触发
    • 当 $Z > 1.5$:残差过高,$Y$ 被高估,$X$ 被低估。做空 $Y$,做多 $X$。
    • 当 $Z < -1.5$:残差过低,$Y$ 被低估,$X$ 被高估。做多 $Y$,做空 $X$。
    • 当 $|Z| < 0.2$:残差回归均值,平仓观望。

2. QMT 策略 Python 源码实现

#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

3. 注意事项与优化建议

  1. 单位根检验(ADF Test):实际回测中,建议在进行 OLS 回归后对残差序列添加 statsmodels.tsa.stattools.adfuller 单位根检验,只有通过检验(p-value < 0.05)时才触发开仓,避免伪回归。
  2. 融券与卖空限制:普通股票账户不支持直接做空 ETF,实盘中若涉及卖空环节,需开通融资融券权限使用融券卖出操作(如 passorder 中的 28: 融券卖出),或仅做单向多头替代。
  3. QMT API 函数:本策略使用 get_market_data_ex 获取历史行情,并结合 passorder 进行比例下单,可有效保证高执行效率。