English Русский Deutsch 日本語
preview
基于协整股票的统计套利(第二部分):EA、回测与优化

基于协整股票的统计套利(第二部分):EA、回测与优化

MetaTrader 5交易系统 |
21 2
Jocimar Lopes
Jocimar Lopes

引言

我们尝试搭建一套面向普通个人交易者的统计套利交易框架。核心思路是整合一系列统计函数与分析方法,让仅配备普通笔记本电脑、标准经纪商账户的个人交易者,也能在外汇、股票、ETF和商品市场开展统计套利交易。

我们最初从简单的基于相关性的配对交易入手,该策略在回测中表现优异,但在模拟账户上却表现糟糕。由此不难得出结论:亏损主要出在订单执行环节。也就是说,策略高度依赖下单速度,而我们的硬件与网络条件(普通笔记本、普通网络、原型代码)远达不到这类套利所需的标准。我们的开平仓规则对时机极其敏感,往往订单还没发到经纪商服务器,套利机会就已经消失。 

我们可以通过一些方式缓解或规避执行速度问题:比如使用VPN(未来可能会尝试)、开通专业账户以放宽滑点限制,或优化原型代码。这些简单且成本不高的措施都会有所帮助,组合使用更可能让策略变得有效。但为了坚守最初的设计目标 —— 让资源有限的普通个人交易者也能用,我们没有执着于提升执行速度,而是转向开发对执行速度不那么敏感的策略。下文实现的方案正是这一思路下的成果。

前一篇文章中,我们介绍了恩格尔 - 格兰杰检验与约翰森协整检验,从交易者角度解释了其原理与基本解读方法。本文将使用这些方法构建协整投资组合。 


构建协整股票组合

醉汉、狗与随机游走

在研究协整时,一个很经典的类比能够很好地解释协整时间序列(或协整股价)的核心特征。两个非协整的时间序列,就像一个醉汉和他的狗一起出门。它们的行进轨迹会随机分离,彼此越走越散,没有内在、可观测或可量化的规律。人和狗甚至可能沿着完全不同的路径回家,狗也可能彻底走失。

而两个协整的时间序列,就像狗被项圈牵住一样。醉汉依旧步履蹒跚,但无论如何,两者始终一起行动。协整股票就像价格被一条“看不见”的绳子拴在一起。长期来看,它们总会一起回到家,这里的“家”,指的是共同的均值水平,也就是价差围绕的那个均值。

但问题是,在数千只证券中,如何找到这类协整股票?幸运的是,我们知道同一行业或板块的股票往往走势趋同。这一经验规律能大幅缩小初选范围,但候选数量依然很多。 

图例1. MetaQuotes模拟服务器上可交易品种数量

图例1. MetaQuotes模拟服务器上可交易品种数量

图例2. 商业经纪商模拟服务器上按国家统计的股票品种数量

图例2. 商业经纪商模拟服务器上按国家统计的股票品种数量

学术文献中已经研究过多种经典以及新型的配对交易候选股票筛选方法,但研究表明,综合对比下来,协整方法的效果优于其他方案[Brunetti & De Luca,2023]。

本文并非学术综述,目的是完成回测并演示一套可落地的示例实现,因此,我采用一套简单的启发式方法来构建小型投资组合。

“启发式(……)是一类务实的问题求解思路,它不追求完全最优、完备或者严谨推导,但作为近似方案已经足够好用。”—— Wikipedia(维基百科)

  1. 手工挑选一批流动性高的纳斯达克半导体板块股票,初始候选池可以选取几十只。
  2. 在这批股票当中,筛选出日线级别、过去6个月及更短周期内与英伟达相关性最高的标的。不要选用过长回溯周期:我们关注近期行情,AI与半导体市场本身变化极快。另外,本模型会按月或按周迭代更新,因此初选采用6个月以内的窗口是比较合适的。
  3. 对这组和英伟达相关性最高的小样本集合进行协整检验。
  4. 一旦检测到至少一个显著的约翰森协整向量,接着检验协整价差序列是否具备平稳性。
  5. 当得到一组样本,存在至少一个显著约翰森向量、协整价差平稳,提取第一个约翰森特征向量,以此得到投资组合各标的的相对权重。 
  6. 获得组合权重后,即可对该协整股票篮子开展回测。

借助前一篇文章介绍的皮尔逊相关系数,我们最终筛选出下面三只股票。三家企业均面向AI硬件需求,直接或通过周边市场融入英伟达供应链。

微芯科技(MCHP):MCHP并不直接为英伟达GPU供货。但随着机器人、工业AI这类AI基础设施需求上涨,基于英伟达平台的OEM厂商与系统集成商会带动整个半导体行业景气,MCHP从中受益。

芯源系统(MPWR):MPWR为服务器与数据中心提供电源管理芯片(PMIC)。高性能英伟达GPU在高负载下需要极其专业的电源管控来保障热稳定性与电气稳定性,这类电源芯片就被应用在其中。英伟达向数据中心与AI推理业务扩张,会带动MPWR业绩增长。

美光科技(MU) :MU提供内存产品,其中包含高带宽内存(HBM),这是英伟达H100这类AI加速芯片的核心器件。MU的内存芯片被集成在英伟达GPU以及各类数据中心方案中。英伟达AI芯片销量越高,对MU高端内存的需求就越旺盛。MU在2024‑2025年给出了受AI驱动的乐观业绩指引,其增长前景与英伟达的表现深度绑定。

重点不在于我们具体选中了哪几只股票。核心思路是:挑选与您选定的参考标的高度相关的股票,该参考标的就是您整套假设的根基。本文选用英伟达NVDA作为基准,您也可以选用其他符合策略逻辑的品种。

我们不希望出现这类标的:单只股票和NVDA相关性极高,但股票彼此之间相关性很低。我们的理想标的是既与NVDA高度相关,同时股票互相之间也保持较高相关性。简单来说,我们需要围绕参考标的形成一组具备联动性的股票集合,这正是手工挑选阶段的前提假设 —— 我们要构建一个具备相关性的股票篮子。

我们需要一份符合上述特征的皮尔逊相关系数矩阵。


NVDA      
MCHP      
MPWR        
MU
NVDA  
1.000000
0.916887
0.894362  
0.897219
MCHP  
0.916887
1.000000
0.877042
0.941977
MPWR  
0.894362  
0.877042  
1.000000
0.852675
MU
0.897219  
0.941977
0.852675  
1.000000

表格1. NVDA、MCHP、MPWR和MU的皮尔逊相关系数矩阵

该组数据可以通过seaborn热力图更加直观地展示。

import seaborn as sns
sns.heatmap(corr_matrix, annot=True, cmap="coolwarm")

图例3. 基于Seaborn热力图展示的NVDA、MCHP、MPWR和MU皮尔逊相关系数矩阵

图例3. 基于Seaborn热力图展示的NVDA、MCHP、MPWR和MU皮尔逊相关系数矩阵

需要注意的是,虽然MCHP、MPWR、MU与英伟达之间并没有极高的相关系数,但这几只股票互相之间均存在中等程度的相关性。

在实盘交易过程中,需要获取四只品种的逐笔Tick数据,才能计算价差,而价差正是用来确定开仓、平仓点位的核心依据。这也是我们一开始就要筛选高流动性品种的主要原因。我们要避开流动性差的标的,这类品种会出现“行情不同步”,或是与OnTick事件处理器的时间偏移超过数秒的情况(见下文)。

确定好这组具备相关性的股票篮子后,我们执行恩格尔‑格兰杰协整检验,评估两两标的之间的协整程度;同时运行约翰森协整检验,评估整个股票篮子,也就是一篮子资产整体的协整水平。关于这两项检验的详细说明,您可以参考前一篇文章。

接下来,我们从约翰森特征向量中提取投资组合对冲系数。

投资组合权重(约翰森特征向量):

MU 2.699439
NVDA1.000000
MPWR-1.877447

MCHP-2.505294

约翰森特征向量是一组数值,组合内每只股票对应一个数值,该数值代表在约翰森检验所识别出的稳定关系中,每只股票所占的权重(重要程度)。利用这些权重,按照特定比例做多、做空一篮子股票进行组合,组合后的价格走势会呈现出具备一定可预测性、大致稳定的形态。

这些权重数值将用于平衡各标的的下单量,并尽可能使组合接近市场中性。要记住,在统计套利策略当中,我们始终追求市场中性。可以把加权投资组合理解为简易配对交易策略的进阶版本:配对交易只是简单地对每个交易标的同时买卖1单位的仓位。

您可以在EA示例的全局变量中找到这套投资组合权重。

// Global variables
string symbols[] = {"MU", "NVDA", "MPWR", "MCHP"}; // Asset symbols
double weights[] = {2.699439, 1.000000, -1.877447, -2.505294}; // Johansen eigenvector

NVDA的取值为1.0,是因为在附带的Python代码中,我们选择以标的列表里第一个资产(即NVDA)作为基准做归一化处理。归一化让我们可以对其余权重数值做相对解读。您也可以选用Python列表中任意其他标的作为归一化基准,该基准只是人为选定的参考。

# === FIRST COINTEGRATION VECTOR ===
v = johansen_result.evec[:, 0]
# v = v / v[-1]  # Normalize on symbols list last asset
v = v / v[0] # Normalize on symbols list first asset

现在,我们就可以检验多元价差序列的平稳性。

图例4. NVDA、MCHP、MPWR和MU的多元协整价差曲线图

图例4. NVDA、MCHP、MPWR和MU的多元协整价差曲线图

对多元价差进行目视检视,是评估其是否适用于均值回归策略的一种有效手段。通过目视观察,可以快速估计价差围绕均值的分布情况、价差向均值回归的行为,以及序列潜在的平稳性。图表上如果看不到明显趋势,通常只是平稳性的一个强烈线索,而非充分证据,不存在季节性特征同样如此。

但仅靠目视检视并不足够,也无法用于自动化模型与投资组合更新流程。好在我们可以借助增广迪基‑富勒(ADF)检验以及Kwiatkowski‑Phillips‑Schmidt‑Shin(KPSS)检验。

通过ADF与KPSS检验,确认该价差具备均值回归特性。

# Augmented Dickey-Fuller Test
from statsmodels.tsa.stattools import adfuller

adf_result = adfuller(spread, regression='c')  # 'ct' for trend and constant
print("ADF Test on Spread:")
print(f"  ADF Statistic : {adf_result[0]:.4f}")
print(f"  p-value       : {adf_result[1]:.4f}")
print(f"  Critical Values:")
for key, value in adf_result[4].items():
    print(f"    {key}: {value:.4f}")

if adf_result[1] < 0.05:
    print("\n✅ The cointegrated spread is stationary (reject the null hypothesis).")
else:
    print("\n❌ The cointegrated spread is NOT stationary (fail to reject the null hypothesis).")

对价差进行ADF检验:

  ADF统计量:-3.2331  
  p值:0.0181

  临界值:

    1%: -3.4704    
    5%: -2.8791

    10%: -2.5761

✅ 该协整价差序列是平稳的(拒绝原假设)。

# KPSS Test
from statsmodels.tsa.stattools import kpss

def run_kpss(series, regression='c'):
    statistic, p_value, lags, crit_values = kpss(series, regression=regression, nlags='auto')
    print("KPSS Test on Spread:")

对价差进行KPSS检验:

  KPSS统计量:0.4142  
  p值:0.0710  
  使用滞后阶数:8

  临界值:

    10%: 0.347    
    5%: 0.463    
    2.5%: 0.574

    1%: 0.739

✅ 该协整价差是平稳的(不能拒绝平稳性的原假设)。

这一步至关重要。如果价差不具备平稳性,我们的均值回归假设就会完全不成立。如果价差非平稳,各资产价格可能持续分化走散,会产生长期无法回归并顺利平仓的持仓。对于这类策略,确认价差具备平稳性是核心前提。

后续随着这套统计套利框架迭代,我们会采用自动化方式轮换调仓投资组合。因此,把这些平稳性检验纳入工具集极为关键,我们不能只依靠图表目视判断。 


示例实现

我们的示例实现,首先以相对于均值的标准差倍数形式,定义开仓与平仓阈值。

// Input parameters
input double EntryThreshold = 2.0;      // Entry threshold (standard deviations)
input double ExitThreshold = 0.3;       // Exit threshold (standard deviations)
input double LotSize = 10.0;            // Fixed lot size per leg
input int LookbackPeriod = 252;         // Lookback for moving average/standard deviation
input int Slippage = 3;                 // Max allowed slippage

默认手数设置为10.0单位,这样在回测过程中可以方便核对订单里的投资组合权重。

此处设置的最大允许滑点更多属于装饰性配置。因为本项目面向普通券商账户,最大滑点限制通常是专业交易账户才具备的功能。但保留该参数便于后续迭代优化,不会产生负面影响。如果交易账户未开启此项功能,经纪商服务器会直接忽略该参数。

计算移动平均与标准差所用的回溯周期,可以和相关性、协整检验使用的周期保持一致,但并非强制要求。举个例子,即便我们在日线级别用过去六个月的数据识别出协整关系,策略完全可以切换到小时周期,使用两周长度的回溯周期进行实际交易。该系统具备充分的灵活性,便于开展各类实验,而这种灵活性也带来大量可挖掘的优化空间。

关于部分特定代码函数的说明。

OnInit()

// Check if all symbols are available
   for(int i = 0; i < ArraySize(symbols); i++)
     {
      if(!SymbolSelect(symbols[i], true))
        {
         Print("Error: Symbol ", symbols[i], " not found!");
         return(INIT_FAILED);
        }
     }

我们会检查标的篮子中的全部品种是否在市场报价窗口(Market Watch)内可用,以此执行报价请求。

// Set a timer for spread, mean, and stdev calculations
   EventSetTimer(1); // one second

我们将价差、均值以及标准差的计算定时器设置在OnTick()函数外部。原因想必您也清楚:OnTick事件处理函数是和EA所挂载的图表/交易品种绑定在一起的。当该品种没有新报价推送时,OnTick就不会被触发。我们不希望计算逻辑依赖该品种的报价更新。通过使用定时器,本例采用1秒时间间隔,可以保证按该时间周期主动获取最新报价。程序由被动接收报价更新,转变为主动轮询获取报价更新。 

OnTimer()

void OnTimer(void)
  {
// Calculate current spread value
   currentSpread = CalculateSpread();
// Update spread buffer (rolling window)
   static int barCount = 0;
   if(barCount < LookbackPeriod)
     {
      spreadBuffer[barCount] = currentSpread;
      barCount++;
      return; // Wait until buffer is filled
     }
// Shift buffer (remove oldest value, add newest)
   for(int i = 0; i < LookbackPeriod - 1; i++)
      spreadBuffer[i] = spreadBuffer[i + 1];
   spreadBuffer[LookbackPeriod - 1] = currentSpread;
// Calculate mean and standard deviation using custom functions
   spreadMean = CalculateMA(spreadBuffer, LookbackPeriod);
   spreadStdDev = CalculateStdDev(spreadBuffer, LookbackPeriod, spreadMean);
  }

在OnTimer事件处理器内部,我们基于该回看窗口更新价差缓冲区,并计算均值和标准差。

OnTick()

void OnTick()
  {
// Trading logic
   if(!tradeOpen)
     {
      // Check for entry signal (spread deviates from mean)
      if(currentSpread > spreadMean + EntryThreshold * spreadStdDev)
        {
         // Short spread (sell MU/NVDA, buy MPWR/MCHP)
         ExecuteTrade(ORDER_TYPE_SELL);
         tradeOpen = true;
        }
      else
         if(currentSpread < spreadMean - EntryThreshold * spreadStdDev)
           {
            // Buy spread (buy MU/NVDA, sell MPWR/MCHP)
            ExecuteTrade(ORDER_TYPE_BUY);
            tradeOpen = true;
           }
     }
   else
     {
      // Check for exit signal (spread reverts to mean)
      if((currentSpread <= spreadMean + ExitThreshold * spreadStdDev) &&
         (currentSpread >= spreadMean - ExitThreshold * spreadStdDev))
        {
         CloseAllTrades();
         tradeOpen = false;
        }
     }
// Display spread in chart
   Comment(StringFormat("Spread: %.2f | Mean: %.2f | StdDev: %.2f", currentSpread, spreadMean, spreadStdDev));
  }

OnTick()事件处理器中仅存放交易逻辑。如果当前没有持仓(!tradeOpen()),并且产生了交易信号,则依据从约翰森特征向量得到的投资组合权重执行买入或卖出操作。

ExecuteTrade(ENUM_ORDER_TYPE orderType)

//+------------------------------------------------------------------+
//| Execute trade with normalized integer lots                |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType)
  {
   double volumeArray[];
   ArrayResize(volumeArray, ArraySize(symbols));
   if(!NormalizeVolumeToIntegerLots(volumeArray, symbols, weights, LotSize))
     {
      Print("Volume normalization failed!");
      return;
     }
   for(int i = 0; i < ArraySize(symbols); i++)
     {
      ENUM_ORDER_TYPE legType = (weights[i] > 0) ? orderType :
                                (orderType == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
      trade.PositionOpen(symbols[i], legType, volumeArray[i], 0, 0, 0, "NVDA Coint");
     }
  }

(...)

//+------------------------------------------------------------------+
//| Normalize volumes to integer lots                             |
//+------------------------------------------------------------------+
bool NormalizeVolumeToIntegerLots(double &volumeArray[], const string &symbols_arr[], const double &weights_arr[], double baseLotSize)
  {
   MqlTick tick; // Structure to store bid/ask prices
   double totalDollarExposure = 0.0;
   double dollarExposures[];
   ArrayResize(dollarExposures, ArraySize(symbols_arr));
// Step 1: Calculate dollar exposure for each leg
   for(int i = 0; i < ArraySize(symbols_arr); i++)
     {
      if(!SymbolInfoTick(symbols_arr[i], tick)) // Get latest bid/ask
        {
         Print("Failed to get price for ", symbols_arr[i]);
         return false;
        }
      // Use bid price for short legs, ask for long legs
      double price = (weights_arr[i] > 0) ? tick.ask : tick.bid;
      dollarExposures[i] = MathAbs(weights_arr[i]) * price * baseLotSize;
      totalDollarExposure += dollarExposures[i];
     }
// Step 2: Convert dollar exposure to integer lots
   for(int i = 0; i < ArraySize(symbols_arr); i++)
     {
      double ratio = dollarExposures[i] / totalDollarExposure;
      double targetDollarExposure = ratio * totalDollarExposure;
      // Get min/max lot size and step for the symbol
      double minLot = SymbolInfoDouble(symbols_arr[i], SYMBOL_VOLUME_MIN);
      double maxLot = SymbolInfoDouble(symbols_arr[i], SYMBOL_VOLUME_MAX);
      double lotStep = SymbolInfoDouble(symbols_arr[i], SYMBOL_VOLUME_STEP);
      // Get current price again (for lot calculation)
      if(!SymbolInfoTick(symbols_arr[i], tick))
         return false;
      double price = (weights_arr[i] > 0) ? tick.ask : tick.bid;
      double lots = targetDollarExposure / price;
      lots = MathFloor(lots / lotStep) * lotStep; // Round down to nearest step
      // Clamp to broker constraints using custom Clamp()
      volumeArray[i] = Clamp(lots, minLot, maxLot);
     }
   return true;
  }

回测

图例5. 纳斯达克协整股票篮子,2025年前五个月日线级别回测的净值曲线

图例5. 纳斯达克协整股票篮子,2025年前五个月日线级别回测的净值曲线

净值曲线说明我们的策略假设具备可行性,但该策略还缺少一套尚未实现的资金管理策略,无法规避部分幅度较大的回撤。

图例6. 纳斯达克协整股票篮子,2025年前五个月日线级别回测汇总报告

图例6. 纳斯达克协整股票篮子,2025年前五个月日线级别回测汇总报告

由此可见,我们所使用的历史价格数据质量很低,但这已是这四只股票能够获取的免费数据源,我们只能基于现有数据开展测试。如果要将该策略投入实盘交易,强烈建议获取质量更高的历史行情数据。

图例7. 纳斯达克协整股票篮子,2025年前五个月日线级别回测:按周期划分的开仓记录

图例7. 纳斯达克协整股票篮子,2025年前五个月日线级别回测:按周期划分的开仓记录

图例8-1. 纳斯达克协整股票篮子,2025年前五个月日线级别回测:持仓时长

图例8-1. 纳斯达克协整股票篮子,2025年前五个月日线级别回测:持仓时长

平均持仓时长约20分钟,这个结果是比较理想的。本文第一部分中初次尝试的失败策略,持仓仅1-2秒,而本策略已经避免了这种情况。

图例8-2. 纳斯达克协整股票篮子,2025年前五个月日线级别回测:MFE(最大有利浮动)与 MAE(最大不利浮动)

图例8-2. 纳斯达克协整股票篮子,2025年前五个月日线级别回测:MFE(最大有利浮动)与 MAE(最大不利浮动)


股票之外的协整应用场景

这是我们第一个同一板块的协整股票篮子,初次测试得到的结果还算尚可。如果进一步完善资金管理,采用更高质量的数据用于参数寻优,该策略就有潜力作为候选方案,放到模拟账户上运行数周做实盘检验。

但您也能发现,这只是一套通用性较强的方案。我们仅选取了小部分纳斯达克股票,同时协整检验回溯窗口、K线周期的组合也十分有限。也就是说,我们只测试了日线级别过去六个月满足协整关系的四只标的。如果您对各类参数做不同组合尝试,能够挖掘出大量值得研究的交易机会。

除此之外,研究对象不必局限于纳斯达克股票,甚至不必局限于股票品类。您可以开展跨资产协整检验,研究股票与ETF、行业指数之间的协整关系。

部分示例

下面是几组在日线级别、采用过去六个月(180个交易日)数据完成协整检验的标的示例。

黄金相关ETF

symbols = ['AAAU', 'USGO', 'BGLD']

1. AAAU —— 高盛实物黄金ETF

“AAAU是一只交易所交易基金,目标是反映金条价格的表现。该基金持有存放于安全金库的实物金条,投资者在满足相应条件的前提下,可以将基金份额赎回为实物黄金。AAAU旨在让投资者直接获得黄金价格敞口,跟踪误差极小,并且不使用衍生品。”

2. USGO —— 安本实物黄金份额ETF

“USGO是由安本投资管理的实物黄金背书型ETF。与AAAU类似,该基金通过持有存放在安全金库内的已分配实物黄金,跟踪金条价格。为投资者提供一种简便、低成本的黄金配置途径,无需自行接收实物黄金。”

3. BGLD —— FT Vest黄金策略目标收益ETF

“BGLD为主动管理型黄金策略ETF,力求在配置黄金资产的同时获取收益。与AAAU、USGO不同,BGLD并不直接持有实物黄金。取而代之,该基金组合使用黄金相关衍生品(期货、期权等)与收益生成策略,实现近似黄金的价格表现,同时重点获取月度收益。”

Index(['AAAU', 'USGO', 'BGLD'], dtype='object')

恩格尔‑格兰杰协整检验结果:

AAAU与USGO|p值:0.1322
AAAU与BGLD|p值:0.0209

USGO与BGLD|p值:0.0144

恩格尔‑格兰杰检验下协整程度最高的配对:USGO与BGLD|p 值:0.0144

请牢记,恩格尔‑格兰杰检验仅检验两两标的之间的关系,该检验无法捕捉约翰森检验所能识别的一篮子多资产整体交互关系。

约翰森检验结果(迹统计量):

观测数量:119

变量数量:3

协整秩0:迹统计量 = 30.21 | 5% 临界值 = 29.80 | 显著
协整秩1:迹统计量 = 10.65 | 5% 临界值 = 15.49 | 不显著

协整秩2:迹统计量 = 4.25 | 5% 临界值 = 3.84 | 显著

通过约翰森检验,我们可以得到一篮子全部资产在各个秩下存在的协整关系数量。

秩0时,迹统计量(30.21)大于 5% 显著性水平临界值(29.80),说明至少存在一组协整向量 。秩1检验结果不显著,但秩2再次显著(4.25>3.84),代表存在第二组协整关系

从恩格尔‑格兰杰的结果可以看到:AAAU‑BGLD(p=0.0209)、USGO‑BGLD(p=0.0144)的协整显著性均高于AAAU‑USGO(p=0.1322)。这说明三项资产内部存在两组相互独立的协整向量,该资产篮子很可能具备较强且稳定的长期均衡关系。

图例9. USGO与BGLD过去六个月日线级别协整价差曲线图

图例9. USGO与BGLD过去六个月日线级别协整价差曲线图

对价差进行ADF检验:

  ADF统计量:-3.7659 
  p值:0.0033

  临界值:

    1%: -3.4870   
    5%: -2.8864

    10%: -2.5800

✅ 价差是平稳的(拒绝原假设)。

对价差进行KPSS检验:

  KPSS统计量:0.1910 
  p值:0.1000;使用的滞后项:5

  临界值:

    10%: 0.347   
    5%: 0.463   
    2.5%: 0.574

    1%: 0.739

✅ 该价差序列是平稳的(不能拒绝平稳性的原假设)。

由此可见,BGLD与AAAU、USGO均存在较强的协整关系。USGO‑BGLD之间的价差平稳性已得到验证,这充分说明,这些标的适合构建黄金相关协整资产篮子。

图例10. AAAU、USGO、BGLD过去六个月日线级别多元协整价差曲线图

图例10. AAAU、USGO、BGLD过去六个月日线级别多元协整价差曲线图

然而,在该时间周期与K线周期下,三者组合得到的协整价差并不具备平稳性 。 

对价差进行ADF检验:

  ADF统计量:-2.7186 
  p值:0.0709

  临界值:

    1%: -3.4870   
    5%: -2.8864

    10%: -2.5800

❌ 该协整价差不具备平稳性(不能拒绝原假设)。

对价差进行KPSS检验:

  KPSS统计量:0.9687 
  p值:0.1000;使用的滞后项:6

  临界值:

    10%: 0.347   
    5%: 0.463   
    2.5%: 0.574

    1%: 0.739

❌ 该协整价差不具备平稳性(拒绝平稳性原假设)。

白银相关ETF

symbols = ['CEF', 'SLV', 'SIVR']

1. CEF —— 思普洛特金银实物信托基金

“这是一支封闭式信托产品,同时持有实物黄金与白银金条。其交易价格相对净资产价值会出现溢价或折价,并非纯粹的白银敞口品种,产品结构与SLV、SIVR存在明显差异。”

2. SLV —— iShares白银信托ETF

“实物白银背书型ETF,目标是跟踪银条价格的表现。它是规模最大、流动性最好的白银ETF之一。”

3. SIVR —— 安本实物白银份额ETF

“SIVR与SLV类似,同为实物白银背书ETF,但通常管理费率更低,对成本敏感型投资者更具吸引力。”

Index(['CEF', 'SLV', 'SIVR'], dtype='object')

恩格尔‑格兰杰协整检验结果:

CEF与SLV|p值:0.6092
CEF与SIVR|p值:0.6109

SLV与SIVR|p值:0.0000

恩格尔‑格兰杰检验下协整程度最高配对:SLV和SIVR|p值:0.0000

约翰森检验结果(迹统计量):

观测数量:121

变量数量:3

协整秩0:迹统计量 = 62.67 | 5% 临界值 = 29.80 | 显著
协整秩1:迹统计量 = 7.20 | 5% 临界值 = 15.49 | 不显著

协整秩2:迹统计量 = 1.95 | 5% 临界值 = 3.84 | 不显著

秩0条件下迹统计量(62.67>29.80)结果显著,说明这三只白银ETF之中至少存在一组协整向量。然而,秩1和秩2均不显著,表明该资产篮子内部仅存在一组稳定的长期均衡关系。

恩格尔‑格兰杰检验结果表明,SLV与SIVR这一组配对具备很强的协整信号(p = 0.0000)。CEF与SLV或SIVR均不存在协整关系(p值大于0.6)。再回看约翰森检验结果,我们可以推断:协整关系大概率来源于SLV与SIVR之间的紧密联系。二者均为实物白银背书ETF,几乎可以互相替代,这也是二者产生协整的合理原因。CEF虽然带有白银敞口,但同时还配置黄金,并且属于封闭式基金,因此,价格行为表现有所不同。

图例11. SLV与SIVR过去六个月日线级别协整价差曲线图

图例11. SLV与SIVR过去六个月日线级别协整价差曲线图

对价差进行ADF检验:

  ADF统计量:-11.0833 
  p值: 0.0000

  临界值:

    1%: -3.4861   
    5%: -2.8859

    10%: -2.5798

✅ 价差是平稳的(拒绝原假设)。

对价差进行KPSS检验:

  KPSS统计量:0.6246 
  p值:0.0204 使用的滞后项:1

  临界值:

    10%: 0.347   
    5%: 0.463   
    2.5%: 0.574

    1%: 0.739

❌ 价差不平稳(拒绝平稳性原假设)。

我们发现SLV与SIVR之间的协整价差,ADF检验判定为平稳,而KPSS检验判定为非平稳。前一篇文章中出现过相似案例,文中也引用了statsmodels库官方文档的相关说明,讲解该如何解读这类相互矛盾的检验结果。

“情况1:两项检验均判定序列非平稳 → 序列非平稳。

情况2:两项检验均判定序列平稳 → 序列平稳。

情况3:KPSS显示平稳、ADF显示非平稳 → 序列为趋势平稳。需要去除趋势,使序列变为严格平稳。对去趋势后的序列进行平稳性检验。

情况4:KPSS检验显示非平稳,ADF检验显示平稳 —— 该序列为差分平稳序列。需要对序列做差分处理,使其变为平稳序列,对差分序列进行平稳性检验。”

图例12. CEF、SLV、SIVR过去六个月日线级别多元协整价差曲线图

图例12. CEF、SLV、SIVR过去六个月日线级别多元协整价差曲线图

对于多元协整价差,即整个资产篮子合成的价差,从走势图能够看出:在该样本周期与K线周期下,价差不具备均值回归特性。平稳性检验的结果也会证实。

对价差进行ADF检验:

  ADF统计量:-0.8780 
  p值: 0.7951

  临界值:

    1%: -3.4865   
    5%: -2.8862

    10%: -2.5799

❌ 该协整价差不具备平稳性(不能拒绝原假设)。

对价差进行KPSS检验:

  KPSS统计量:1.3918 
  p值:0.0100 

  使用滞后阶数:6

  临界值:

    10%: 0.347   
    5%: 0.463   
    2.5%: 0.574

    1%: 0.739

❌ 该协整价差不具备平稳性(拒绝平稳性原假设)。

这又意味着什么?当然,在本例中我们并没有得到一组合适的白银ETF资产篮子,但找到了两只协整程度极高的ETF(SLV与SIVR),非常适合开展配对交易,配对交易正是本系列第一篇文章探讨的主题。

以上两个示例意在说明:在挖掘统计套利所需的协整资产标的时,实验与研究是关键所在。同时,我们需要牢记,单纯依靠统计指标,并不能替代您对行业板块的理解以及市场认知。


结论

本文介绍了一款示例EA,该EA针对一小组协整股票完成回测。本文是系列两篇文章中的第二篇,讲解构建统计套利投资组合最常用的统计指标:相关系数、协整评估,以及价差平稳性检验。

值得注意的是,这类策略几乎很难应用于外汇货币对,因为很难找到两种以上存在协整关系的货币;但该策略同样适用于指数、ETF以及大宗商品。 

既然我们现在已经掌握基于协整构建投资组合的基础工具,接下来合理的开发方向可以是实现投资组合实时轮换调仓,未来还可以进一步演进为机器学习方案。

参考文献

Marianna Brunetti & Roberta De Luca, 2023. 《基于协整的配对交易标的预筛选》,发表于《Statistical Methods & Applications》,施普林格出版社;意大利统计学会,第32卷第5期,1611‑1640页,12月。

备注

* 本文所用的随机游走概念虽有借鉴来源,但请勿将其与皮尔逊随机游走相混淆,后者是由这位英国数学家提出并命名的随机过程。

附件 描述
corr_pearson.ypng 该文件为Jupyter笔记本,内含Python代码。脚本用于执行皮尔逊相关性检验。
coint_stocks_ETFs.ypnb 该文件同样是Jupyter笔记本,内含Python代码。脚本可执行恩格尔‑格兰杰检验、约翰森协整检验,以及ADF和KPSS平稳性检验。
Nasdaq_NVDA_Coint.mql5 该文件为本篇回测所使用的示例EA源代码。
Nasdaq_NVDA_Coint.ini 该文件为ini格式配置文件,存放回测使用的各项参数。
Nasdaq_NVDA_Coint.NVDA.Daily.20250101_20250515.021.ini 该文件同样为ini配置文件,保存回测过程使用的优化参数。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/19052

附加的文件 |
coint-stocks.zip (128.6 KB)
最近评论 | 前往讨论 (2)
Stanislav Korotky
Stanislav Korotky | 12 4月 2026 在 11:50

为什么要搞得这么复杂?

dollarExposures[i] = MathAbs(weights_arr[i]) * price * baseLotSize;
...
double ratio = dollarExposures[i] / totalDollarExposure;
double targetDollarExposure = ratio * totalDollarExposure;
...
double lots = targetDollarExposure / price;

这得出的结果是:

lots -> ratio * totalDollarExposure / price -> dollarExposures[i] / totalDollarExposure * totalDollarExposure / price -> dollarExposures[i] / price -> MathAbs(weights_arr[i]) * baseLotSize

此外,您的计算结果与账户货币以外的货币符号不兼容。

Jocimar Lopes
Jocimar Lopes | 27 4月 2026 在 15:43
Stanislav Korotky #:

为什么要搞得这么复杂?

结果如下:

此外,您的计算结果与以账户货币以外的货币标注的符号不兼容。

谢谢你,斯坦尼斯拉夫!
MQL5交易工具(第八部分):具备可拖拽与可最小化功能的增强型信息仪表盘 MQL5交易工具(第八部分):具备可拖拽与可最小化功能的增强型信息仪表盘
在本文中,我们将开发一套增强型信息仪表盘。该版本在上一篇内容的基础上进行升级,新增可拖拽和最小化功能以优化用户交互,同时保持对多品种持仓与账户指标的实时监控能力。
Python-MetaTrader 5 策略测试器(第一部分):交易模拟器 Python-MetaTrader 5 策略测试器(第一部分):交易模拟器
Python 提供的 MetaTrader 5 模块支持通过 Python 在 MetaTrader 5 客户端发起交易,但存在一个重大缺陷:该模块不具备 MetaTrader 5 客户端自带的策略测试器功能。在本系列文章中,我们将搭建一套框架,实现基于 Python 环境的交易策略回测。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
我们如何打造最强大的机器学习驱动交易平台:通过档案、论坛与发布记录回顾 MQL 与 MetaTrader 的演进 我们如何打造最强大的机器学习驱动交易平台:通过档案、论坛与发布记录回顾 MQL 与 MetaTrader 的演进
MQL 演进的技术史:从功能有限的 MQL 和 MQL II 语言,到过程式的 MQL4,再到具有原生编译功能、丰富 API 和完备工程环境的面向对象的 MQL5。我们在此展示该语言的关键特性,以及其与 Python、OpenCL、ONNX、OpenBLAS、数据库、DirectX、代理式人工智能助手(Agentic AI Assistant)及模型上下文协议(MCP)的集成情况;其中,模型上下文协议(MCP)可将人工智能系统与终端、MetaEditor、市场数据、交易操作及开发工具相连接。本文探讨了 MetaQuotes 和 MetaTrader 的起源、MQL4.COM 和 MQL5.COM 的推出、各类锦标赛、Algo Forge 及其对生态系统的影响等档案资料。