股票协整检验与配对交易:从ADF到策略实现的完整指南
简介面向股票量化分析与时间序列研究者这份压缩包聚焦协整性检验结合周期共振与多标的同步分析可揭示多只股票价格序列间的长期均衡关系及短期偏离规律适用于配对交易或均值回归策略的前期研究。压缩包共十个文件包括执行协整分析流程的脚本、三只股票的原始数据表、记录两种单位根检验步骤与解读的文档以及展示差分前后序列变化的可视化图整体大小约三百五十一千字节轻量便携。文档对比了直接回归与差分后回归两种处理路径并以图形直观呈现协整关系和差分效果适合具备一定统计学基础、希望掌握时间序列方法的中级数据分析师。已有两百九十六人学习下载通过该资源可获得可复跑的完整代码、整理好的实验数据、图文并茂的分析报告并能将分析框架迁移到其他股票组合为量化建模提供实证参考。1. 为什么同一套协整代码换两只股票就失灵做配对交易的人大概都遇到过这种尴尬用某只股票组合跑协整检验p-value漂亮得能当壁纸可一旦把股票代码换成另一组同样的流程立刻返回一堆非平稳的残差。问题通常不出在代码而出在很多人把协整分析做成了最小二乘回归 ADF 检验的流水线忽略了一个关键前提——两组价格序列的阶数是否一致以及回归之后残差是不是真的被检验到了点上。这个资源包里给出的股票协整性分析.py和两组 ADF 结果正好把这个问题拆开了一组是直接回归再对残差做 ADF另一组是各自差分后再回归的对照。600776、002409、300473 这三只票从日线价格上看都不是平稳序列但一阶差分后大概率平稳这种情况在金融时间序列里叫 I(1) 过程。协整的意义就在于虽然单看每只股票价格乱走但它们之间存在一个线性组合这个组合本身是平稳的那这个组合就可以拿来构造均值回归策略。明白这个前提之后再看包里的文件就有意思了。直接回归adf.docx是拿原始价格直接跑 OLS 然后对残差做adfuller()差分后回归adf.docx是先对每只股票做一阶差分、再回归、再跑 ADF。前者若能通过说明价格之间存在稳定的线性关系后者能通过说明关系体现在变动量层面这对构建交易信号的指导意义完全不同。下面从操作层面把两条路线的原理、代码和判读标准说透顺便把常见报错和误判点也指出来。2. 协整检验的完整流程与coint函数用法协整检验不是跑一下statsmodels.tsa.stattools.coint就完事这么简单。它背后是 Engle-Granger 两步法先用 OLS 估计长期均衡关系再对残差做单位根检验。这个包里的股票协整性分析.py实际上就是把这两步手工展开好处是对残差序列有完全的控制权坏处是如果中间某一步序列阶数不一致结果就会出现看起来显著、实际上没用的假象。2.1 Engle-Granger 两步法的代码实现常见做法是拿原始价格做 OLS然后对残差做 ADF。下面的代码是资源里股票协整性分析.py这类脚本的核心片段用 pandas 和 statsmodels 就能跑通import pandas as pd import numpy as np from statsmodels.tsa.stattools import adfuller, coint import statsmodels.api as sm # 读取三只股票的收盘价序列假设df.csv包含日期和价格列 df pd.read_csv(df.csv, index_coldate, parse_datesTrue) df df[[600776, 002409, 300473]].dropna() # 直接回归用600776作为因变量另两只作为自变量 X df[[002409, 300473]] X sm.add_constant(X) ols_model sm.OLS(df[600776], X).fit() residual ols_model.resid # 对残差做ADF检验判断残差是否平稳 adf_result adfuller(residual, autolagAIC) print(fADF统计量: {adf_result[0]:.4f}) print(fp值: {adf_result[1]:.4f}) for key, value in adf_result[4].items(): print(f临界值({key}): {value:.4f}) # 对比直接使用statsmodels的coint函数内置EG两步法 coint_t, coint_p, coint_crit coint(df[600776], df[[002409, 300473]]) print(fcoint-t: {coint_t:.4f}, coint-p: {coint_p:.4f})这段代码的逻辑是先读入三只股票价格dropna()让序列对齐然后以 600776 为因变量做 OLS得到残差序列对残差做adfuller时用的是 AIC 自动选择滞后阶数这一步是为了确认残差不含单位根。sm.add_constant是给回归加上截距项这在股票价格回归里是必须的因为两只股票的绝对价格水平通常不在一个数量级。ADF 检验的判定标准是看 p 值小于 0.05 说明残差平稳即存在协整关系大于 0.05 则无法拒绝残差含单位根的原假设说明两条价格序列的线性组合不稳定不能直接用来构造配对交易信号。这里还有一个容易忽略的细节adfuller默认带常数项不带趋势项股票价格回归的残差确实不应该有趋势如果发现残差有明显的线性趋势就要回头检查是不是漏了什么结构性断点。资源里的直接回归adf.png应该是把这段代码跑出来的残差走势和 ADF 结果贴在了一张图上看到残差曲线在零轴上下随机摆动、没有明显的单边漂移才能初步判断方向是对的。2.2 回归之后再看差分两种回归的根本区别协整性600776-002409-300473差分后回归adf.png对应的做法是把每只股票的价格序列先diff()一次再用差分后的序列做同样的 OLS 和 ADF。这段处理在 python 里几乎不增加代码量df_diff df.diff().dropna() # 同样以600776为因变量做回归 X_diff sm.add_constant(df_diff[[002409, 300473]]) ols_diff sm.OLS(df_diff[600776], X_diff).fit() resid_diff ols_diff.resid adf_diff adfuller(resid_diff, autolagAIC) print(f差分后残差ADF p值: {adf_diff[1]:.4f})差分后的回归和直接回归在数学上描述的是完全不同的关系。直接回归说600776 的价格约等于另外两只股票价格的线性组合加上一个稳定误差差分后回归说600776 的涨跌幅约等于另外两只涨跌幅的线性组合加上一个稳定误差。后者对做日频交易的参考意义更大因为它拟合的是收益率的联动而不是价格水平的联动。从结果上看直接回归那一组如果残差 ADF 通过说明三只股票存在长期均衡的价格关系适合做均值回归——偏离了这个均衡关系就开仓回归了就平仓。差分后回归如果通过说明收益率的同步性高那不是均值回归逻辑而更像是动量共振参考价值更多体现在不同股票之间能否相互预测短期涨跌。操作时最容易犯的错误就是把这两类结果混着解读拿差分后回归的显著性去支撑价格会回归的判断这等于把两个不同假设的检验统计量放在同一个决策框架里钱亏了都不知道是策略错还是检验结果用错。3. ADF 检验的参数选择与三张图的判读方法ADF 检验是协整分析的核心裁判但裁判的尺子不是固定的。adfuller函数的参数直接决定检验结论是否可信。资源里的三张 png 输出分别用价格水平直接回归残差和差分后回归残差做了对比演示看懂这三张图其实就理解了协整分析在股票数据上最常见的呈现方式。3.1 ADF 检验的三个关键参数# 标准写法指定回归项和滞后阶数选择方式 adf_result adfuller( residual, maxlag20, # 最大滞后阶数 regressionc, # 含常数项c可选ct含趋势项n无常数项 autolagAIC # 自动选择滞后阶数 )参数说明maxlag控制允许的最大滞后阶数日频收益率数据一般取 10 到 30 都能覆盖较强的自相关结构regressionc表示检验回归方程中带常数项这是金融价格残差序列的标准配置autolagAIC让函数在 0 到maxlag之间自动挑最优滞后阶数避免手动设置滞后阶数带来的主观偏差。regressionct含趋势项这个选项在股票协整中要特别小心。如果序列本身没有确定性趋势却硬加一个趋势项检验功效会下降本来应该拒绝单位根原假设的残差反而可能被判为不平稳。只有当直接回归的残差图明显出现一条斜线——也就是价差长期在零轴上方向单一漂移——才考虑用趋势项重新检验。包里的协整性600776-002409-300473差分.png展示的应该是一阶差分后的价格走势。用差分后的序列做检验时数据里二阶或更高阶的单位根已经被消掉剩下的问题只是残差平不平稳。两块图连起来看的意义在这里价格水平图能看出三只票大方向一致但节奏不完全同步差分图能看出波动聚集现象——涨跌幅大的时段三只票同步放大平稳时段同步收窄这正是金融时间序列里常见的波动率聚集特征。3.2 看图定结论序列图和 ADF 输出图的配合做协整检验不能只看 p 值必须把残差序列图和 ADF 输出合在一起看才有说服力。观察对象平稳残差特征非平稳残差特征残差时序图围绕 0 或均值附近随机波动无趋势、无周期有长周期漂移偏离后不回归自相关图各阶自相关迅速衰减到置信区间内低阶自相关显著不为 0衰减缓慢ADF 统计量绝对值明显大于 1% / 5% 临界值绝对值小于 5% 临界值ADF 输出的结果是一个伴随统计量一般不用单独看t-stat的绝对大小直接看 p 值即可p 小于 0.05 拒绝单位根原假设。但要留意的是ADF 检验在样本量只有 100 到 200 个交易日时会变得非常不敏感小样本下即使残差真平稳p 值也可能大于 0.1。所以对日频股票数据至少要用一年以上的历史数据250 个交易日以上才有统计意义。资源里600776-002409-300473直接回归adf.png的样本量从图上应该能看出来如果不到 200 个点哪怕 p 值勉强小于 0.05 也要留个心眼。样本量范围ADF 可靠性建议操作100 个交易日很低补充数据或弃用结果100250一般结合残差图二次确认250较好正常放行当三张图形成证据链——价格序列同涨同跌、价差序列在固定区间内波动、差分后回归的 ADF p 值显著——这个组合才真正有实际交易含义。缺任何一环协整结论都可能是统计幻觉。4. 从协整组合到配对交易策略回测与调参协整检验通过只是第一步把它变成可执行的交易策略才见功力。资源包的df.csv和分文件df1.csv、df2.csv应该是不同时间窗口或不同复权方式的数据集正好拿来做参数稳健性验证。我一般会先在同一份数据上跑出协整组合再换到另一份数据上检验同样的参数是否仍然有效。4.1 z-score 价差信号与参数表标准配对交易框架长这样用滚动窗口估算协整系数实时计算价差当价差偏离均值超过阈值开仓回归均值时平仓。# 核心策略实现在coint结果基础上做z-score def generate_signals(price_a, price_b, coint_coef, window20, entry2.0, exit0.5): # 构造价差spreadprice_a - coef * price_b spread price_a - coint_coef * price_b # 滚动均值与标准差 mean spread.rolling(window).mean() std spread.rolling(window).std() zscore (spread - mean) / std position np.zeros(len(spread)) # zscore上穿entry做空spread下穿-exit平仓 position[zscore entry] -1 position[zscore -entry] 1 position[zscore.abs() exit] 0 return pd.Series(position, indexprice_a.index).fillna(0)关键参数集中在entry、exit和window三个值上。entry是开仓阈值设为 2.0 意味着只有当价差偏离均值超过两个标准差才进场阈值太小会让策略在价差正常波动区间内反复开平仓交易成本吃掉收益阈值太大则交易机会变少一年下来可能只有几次入场。exit是平仓阈值常见做法是设到 0.5 或直接设成 0意思是价差回归均值附近就退出。window控制滚动统计的窗口长度既要足够长让均值稳定又不能太长导致对近期市场结构的变化反应迟钝。4.2 参数敏感性与换期检验任何协整策略都逃不过一个灵魂拷问协整关系是数据挖掘出来的还是真实存在的解决这个问题有一个很笨但有效的办法——换样本期验证。验证方式做法通过标准前段/后段分割前半段找协整组合后半段看策略表现后段收益为正且回撤可控换股票验证用同一算法在代码 K 里重跑出现类似比例的显著组合即可滚动协整检验每次用 250 日窗口滚动做coint协整关系持续存在不频繁消失用这个标准去回看资源里的df.csv和df2.csv如果两个文件是不同行情区间那么一只股票组合只有在两份数据里都能通过协整检验同时策略参数在两个区间绩效都稳定才能说明这个组合值得投入真金白银。如果只在某一小段样本内显著换一段数据就失效那就只是过拟合。反过来如果组合能稳定通过用它跑特色策略就有底气得多。这里给出的策略代码是保证金交易时代最常见的基础设置真实运行时还会加上仓位管理和手续费模拟。做回测时注意别把手续费设成 0否则 ADF 检验的显著性本身无法覆盖掉交易摩擦前者解决的是统计问题后者解决的是成本问题。5. 用单整阶数对齐绕开协整检验的常见误伤协整分析里最隐蔽的问题不是参数没调对而是把不同单整阶数的序列强行塞进回归。GDP 和股价、成交量和价格这些序列单看都不是平稳的但它们之间可能根本不存在协整关系。要搞清楚一组序列能不能做协整先得回答各自是不是 I(1)这就需要对每一只股票单独做 ADF 检验。5.1 单整阶数检验的快速套用资源包里没有单独给出这一步骤的脚本但这是几乎每个工程师接手协整分析后最常用的补充逻辑直接用adfuller就能完成def check_stationarity(series): 检查序列平稳性返回单整阶数 # 判断原序列是否平稳 adf_t, adf_p, *_ adfuller(series, autolagAIC) if adf_p 0.05: return 0 # 本身平稳I(0) # 一阶差分后再检验 diff_t, diff_p, *_ adfuller(series.diff().dropna(), autolagAIC) if diff_p 0.05: return 1 # 差分后平稳I(1) return 2 # 需要二阶差分 # 对三只股票分别检查 for col in [600776, 002409, 300473]: order check_stationarity(df[col]) print(f{col}: I({order}))这段代码逻辑并不复杂先对原序列做 ADFp 小于 0.05 直接判定 I(0)不行就对一阶差分再做一次 ADF通过的就是 I(1)。之所以要有这一步是因为 Engle-Granger 两步法对阶数非常敏感两只股价分别是 I(1) 和 I(2)回归残差必然不平稳别说协整了连伪回归这个帽子都摘不掉。值得注意的是df1.csv和df.csv如果取自不同时间段同一只股票的单整阶数应该一致——价格序列的一阶差分反映的是日度收益理论上不随时间窗口改变而改变。如果同一只股票在前半段是 I(1)后半段检验出来是 I(0)说明股价在某一时期本身有均值回归特征震荡市常见这种状态下协整的意义就会减弱。5.2 ADF 与 Johansen 的适用场景分界Engle-Granger 两步法的弱点在于它只能估计一组协整关系当处理三只以上股票时可能存在的协整向量不止一个OLS 只能捕捉其中一个。更规范的做法是升级到 Johansen 检验statsmodels 提供对应接口from statsmodels.tsa.vector_ar.vecm import coint_johansen # 检查三只股票价格之间是否存在多个协整向量 johansen_res coint_johansen(df[[600776, 002409, 300473]], det_order0, k_ar_diff1) print(ftrace统计量: {johansen_res.lr1}) print(f最大特征值统计量: {johansen_res.lr2})协整向量数量超过 1 时EG 两步法给出的回归系数实际上只是无数个协整向量中的一个不一定对应真实的均衡关系。此时建议以 Johansen 检验的 trace 统计量为主判据股票数量越多越应该依赖 Johansen 而不是简单回归加 ADF。反过来如果只处理一对一的配对交易EG 两步法更直观回归系数本身就是对冲比例。实际上这个资源包里的三只股票组合用 Johansen 检验的统计意义更大——三只股票之间到底存在几个协整关系决定了策略到底是两两配对还是一组组合共同回归。分清这个区别才能决定后续策略构造方式这也是从用协整检验走向用协整做策略的重要分水岭。6. 解读前 100 字里的信息差从统计结果到交易决策的最后一公里协整分析最有价值的地方其实不是它的结论而是它的边界——数据说明的是统计意义上的长期均衡而不是交易意义上的稳定盈利。资源包里给出的两组 ADF 对比图恰好用来做决策流程上最后的把关。看直接回归adf.png里统计量的显著性之前先注意样本周期内有没有除权除息跳空缺口。a 股经常出现 10 送 10、大比例分红导致的股价跳空这种跳空会让价格序列产生一个永久性断点ADF 检验即便通过了残差图上看得见的结构突变也会在实盘交易中制造假信号价差一旦跳到断点另一侧就再也回不来。这种问题看起来是统计显著性问题实际上却是数据清洗的锅会直接造成协整策略回测中的过度乐观和实盘净值难以复制。比如 600776 的季K线图上如果存在明显的除权跳空在回测时会表现为价差必然回归——因为除权日后价格一次性调整价差自然修复——而这种修复没有任何套利价值。处理方案是在做协整检验之前对前复权价格做断点检测具体做法如下# 检测价格序列中的跳空断点简单阈值法 def detect_gap(price, pct_threshold0.15): 检查是否存在超过15%的隔日跳空 ret price.pct_change().dropna() gap_days ret[abs(ret) pct_threshold].index return gap_days gaps detect_gap(df[600776]) if len(gaps) 0: # 发现除权跳空剔除跳空点后再做协整分析 print(f检测到{gaps.tolist()}) df_clean df.loc[~df.index.isin(gaps)]处理除权跳空这个动作看起来微不足道但它决定了回测结果能否外推。数据清洗干净后资源里直接回归adf.docx和差分后回归adf.docx中给出的统计结论才真正具备参考价值。统计工具本身没有错错的是在不该用它的地方用了它——协整分析天生适合那些长期均衡关系真实存在的资产组合比如同一板块内的同产业链股票而不适合拿来进行随机组合的正态性挖掘。三只票如果分别处于完全无关的行业即便 ADF 通过市场逻辑也撑不起这个统计结果。与其把精力放在调参不如回顾一下两个图库文件的结论来对照自己的数据状况。df.csv那份跑出的协整结果如果在后来的数据里失效不要先怀疑 python 代码写错了优先检查是否经历了除权或退市风险警示这两类事件都会导致长期关系结构性破裂。真正的协整不是永恒定律而是需要定期重新估计的关系回归过程。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →