协整与伪回归:如何找到真正一起走的股票对
“相关性是市场的表象,协整才是均值回复的底层逻辑。”
2010 年秋天,一只量化基金在纽交所的对决震惊了华尔街。
主人公是斯坦福统计学教授 Alfred Nobel 的门徒——David Shaw;对手是一家体型十倍于他的对冲基金。两者都发现了同一个“机会”:美国电力股与天然气股走势高度相关,价差长期围绕某个均值波动。逢高做空电力股、逢低做多天然气股,看起来是稳赚不赔的均值回复策略。
两家基金几乎同时建仓。然后市场没有回复。价差不仅没有收窄,反而在接下来六个月里扩大了 40%。Shaw 被迫止损,对手基金爆仓。
问题出在哪里?他们的模型基于相关性。相关性高意味着两个序列"一起动",但不意味着"一起回来"。这两个看似相近的概念,埋藏着配对交易最古老也最致命的陷阱。
一、为什么"一起涨"不等于"一起回来"
1.1 相关的局限
pandas 计算相关系数只需要一行代码,任何人都能在三分钟内发现两只股票的关联。但相关性有几个先天缺陷:
| 缺陷 | 表现 | 示例 |
|---|---|---|
| 非平稳敏感 | 相关性对价格趋势免疫,但很多金融序列是"漂移的" | 茅台和五粮液 2015-2020 年都涨了 10 倍,相关系数 0.95,但两者从不均值回复 |
| 伪回归陷阱 | 对非平稳序列做 OLS 回归,t 值和 R² 看起来漂亮,但统计推断完全失效 | 两只无关的随机游走序列,相关系数也能达到 0.7 以上 |
| 忽略量纲 | 相关性是无量纲的,无法告诉你"偏离多少算极端" | 贵州茅台与某 ETF 相关系数 0.88,但你无法基于此构建可执行的风控框架 |
一个经典的蒙特卡洛实验能直观说明问题:用两个独立的随机游走序列 $X_t = X_{t-1} + \varepsilon_t$ 和 $Y_t = Y_{t-1} + \eta_t$,两者在生成上毫无关联。但在 252 个交易日(一年)之后,它们的样本相关系数超过 0.7 的概率大约是 12%。这意味着每八个随机生成的"无关股票对"中,就有一个看起来"高度相关"。
1.2 平稳性:理解一切的起点
在谈协整之前,必须先理解平稳性(Stationarity)。
一个时间序列 ${X_t}$ 是严平稳的,当且仅当任意时间窗口的联合分布与起点无关。直观理解:一杯咖啡的温度在不同时间测量,分布特征是稳定的,不会随"什么时候测"而变化。
金融数据中,大多数资产价格是非平稳的——它们有单位根(Unit Root),会"漂移"。用白话讲:今天的价格和明天的价格没有稳定的均值关系,价格本身可以无限走远。
检验平稳性的标准方法是 ADF 检验(Augmented Dickey-Fuller Test):
$$
\Delta y_t = \alpha + \beta t + \gamma y_{t-1} + \sum_{i=1}^p \delta_i \Delta y_{t-i} + \varepsilon_t
$$
原假设 $H_0$:$\gamma = 0$(序列存在单位根,非平稳)
备择假设 $H_1$:$\gamma < 0$(序列平稳)
若 p 值小于显著性水平,则拒绝原假设,认为序列平稳。
1.3 协整的定义
现在回到 David Shaw 的案例。如果电力股和天然气股都沿着同一个趋势线"漂移",而价差(Spread)是围绕趋势线的波动——那么电力股和天然气股协整(Cointegration)。
协整的数学定义比相关性严格得多:
如果两个(或多个)非平稳序列 ${X_t}$ 和 ${Y_t}$,存在一个线性组合 $Z_t = X_t - \beta Y_t$,使得 ${Z_t}$ 是平稳的,则称 ${X_t}$ 和 ${Y_t}$ 协整,记作 $CI(1,1)$。
关键词是线性组合后的平稳性。这与相关性有本质区别:
| 属性 | 相关性 | 协整 |
|---|---|---|
| 对象 | 任意两个序列 | 两个(或多个)同阶单整序列 |
| 衡量 | 共同趋势的幅度 | 偏离均衡后的回复能力 |
| 可逆性 | 对称($\rho_{XY} = \rho_{YX}$) | 不对称($\beta$ 有方向) |
| 统计推断 | 在非平稳序列上失效 | 需要严格检验 |
举一个生活化的例子:遛狗时人和狗都在随机游走(都是非平稳的),但狗与人的距离——也就是"绳子绷紧的程度"——是平稳的。人不会一直往前走而不停下来,狗也不会一直跑而不回头。协整就是这根绳子。
二、三步验证法:从选股到确认协整
完整的协整分析分为三个步骤,每一步都不可或缺。
2.1 步骤一:平稳性检验(ADF)
首先对候选股票对各自做 ADF 检验,确认它们是同阶单整的。在金融场景下,几乎所有资产价格都是 I(1)——一阶差分平稳,因此这一步通常可以直接进行,但严格流程中不应跳过。
2.2 步骤二:协整回归与残差检验
用 OLS 拟合:
$$
X_t = \alpha + \beta Y_t + \varepsilon_t
$$
然后对残差 ${\hat{\varepsilon}_t}$ 做 ADF 检验。如果残差平稳,则 ${X_t}$ 和 ${Y_t}$ 协整。
这个方法叫做 Engle-Granger 两步法,是协整检验的经典框架。
2.3 步骤三:结果解释与限制
协整关系不等于因果关系。即使两只股票协整,也不意味着它们存在经济逻辑上的因果驱动。协整只说明"它们不会背离太远太久"。此外,协整关系可能是非稳定的——随着时间推移,历史上协整的股票对可能逐渐失去这一特性。
下面用 Python 演示完整流程。
三、生产级代码:从数据获取到协整检验
本节代码覆盖完整的协整检验流程,包括历史数据获取、ADF 检验、协整回归和结果解读。所有 API 调用均使用 TickDB REST 接口,包含鉴权、超时和错误处理。
"""
配对交易:协整性检验完整流程
基于 TickDB K 线数据,执行 Engler-Granger 两步法协整检验
"""
import os
import time
import json
import random
import warnings
import numpy as np
import pandas as pd
import requests
from statsmodels.regression.linear_model import OLS
from statsmodels.tools import add_constant
from statsmodels.tsa.stattools import adfuller
warnings.filterwarnings("ignore")
# ─────────────────────────────────────────────
# 1. TickDB API 客户端(生产级实现)
# ─────────────────────────────────────────────
class TickDBClient:
"""
TickDB REST API 客户端
生产级特性:
- 环境变量存储 API Key
- 超时设置(connect=3.05s,read=10s)
- 限频处理(code:3001 + Retry-After)
- 基础错误重试(指数退避 + 抖动)
"""
def __init__(self, api_key: str = None, base_url: str = "https://api.tickdb.ai/v1"):
self.api_key = api_key or os.environ.get("TICKDB_API_KEY")
if not self.api_key:
raise ValueError("请设置环境变量 TICKDB_API_KEY")
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.headers.update({"X-API-Key": self.api_key})
def _request(self, method: str, path: str, params: dict = None,
max_retries: int = 3, base_delay: float = 1.0) -> dict:
"""带指数退避的 HTTP 请求封装"""
url = f"{self.base_url}{path}"
for attempt in range(max_retries):
try:
response = self.session.request(
method,
url,
params=params,
timeout=(3.05, 10) # (connect, read)
)
data = response.json()
# 限频处理
if data.get("code") == 3001:
retry_after = int(response.headers.get("Retry-After", 5))
print(f"[限频] 等待 {retry_after} 秒后重试...")
time.sleep(retry_after)
continue
if data.get("code") == 0:
return data.get("data", data)
# 可重试错误
if data.get("code") in (1001, 1002):
raise ValueError(f"API 认证失败: {data.get('message')}")
# 非可重试错误
raise RuntimeError(f"API 错误 {data.get('code')}: {data.get('message')}")
except requests.exceptions.Timeout:
if attempt < max_retries - 1:
delay = base_delay * (2 ** attempt)
jitter = random.uniform(0, delay * 0.1)
time.sleep(delay + jitter)
continue
raise
except requests.exceptions.RequestException as e:
if attempt < max_retries - 1:
delay = base_delay * (2 ** attempt)
jitter = random.uniform(0, delay * 0.1)
time.sleep(delay + jitter)
continue
raise
raise RuntimeError("达到最大重试次数")
def get_kline(self, symbol: str, interval: str = "1d",
start_time: int = None, end_time: int = None,
limit: int = 500) -> pd.DataFrame:
"""
获取 K 线数据
Args:
symbol: 交易品种,如 "JPM.US"
interval: K 线周期,支持 1m/5m/1h/4h/1d/1w
start_time: 毫秒时间戳
end_time: 毫秒时间戳
limit: 单次请求最大条数(TickDB 上限)
"""
all_data = []
while True:
params = {
"symbol": symbol,
"interval": interval,
"limit": limit,
}
if end_time:
params["end_time"] = end_time
records = self._request("GET", "/market/kline", params=params)
if not records or len(records) == 0:
break
all_data.extend(records)
# 用最后一条记录的时间戳继续向前拉
end_time = records[0].get("time")
if len(records) < limit:
break
# ⚠️ 高频请求适当限速
time.sleep(0.05)
if not all_data:
return pd.DataFrame()
df = pd.DataFrame(all_data)
df["time"] = pd.to_datetime(df["time"], unit="ms")
df = df.sort_values("time").reset_index(drop=True)
numeric_cols = ["open", "high", "low", "close", "volume"]
for col in numeric_cols:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
return df
# ─────────────────────────────────────────────
# 2. 协整检验模块
# ─────────────────────────────────────────────
def adf_test(series: pd.Series, significance: float = 0.05) -> dict:
"""
ADF 平稳性检验
Returns:
dict: {
"statistic": float, # ADF 统计量
"p_value": float, # p 值
"is_stationary": bool,
"critical_values": dict
}
"""
result = adfuller(series.dropna(), maxlag=12, regression="c")
# regression="c": 包含常数项,无趋势项
# 金融价格序列通常选择含常数项的设定
statistic, p_value, lags, used_lag, critical_values, *_ = result
return {
"statistic": statistic,
"p_value": p_value,
"is_stationary": p_value < significance,
"critical_values": {k: v for k, v in critical_values.items()},
"used_lag": used_lag
}
def cointegration_test(series_x: pd.Series, series_y: pd.Series,
significance: float = 0.05) -> dict:
"""
Engle-Granger 两步法协整检验
步骤:
1. OLS 回归:X = α + β·Y + ε
2. ADF 检验残差 ε 是否平稳
Args:
series_x: 候选配对资产 X(收盘价)
series_y: 候选配对资产 Y(收盘价)
significance: 显著性水平(默认 5%)
Returns:
dict: 完整的协整检验结果
"""
# 对齐数据
aligned = pd.DataFrame({"x": series_x, "y": series_y}).dropna()
if len(aligned) < 30:
return {"error": f"数据点不足({len(aligned)}),至少需要 30 个观测值"}
# 步骤一:OLS 协整回归
y_reg = aligned["x"]
x_reg = add_constant(aligned["y"]) # 添加常数项
model = OLS(y_reg, x_reg).fit()
residuals = model.resid
# 步骤二:残差平稳性检验
adf_result = adf_test(residuals, significance)
# 计算价差(Spread)
beta = model.params["y"]
alpha = model.params["const"]
spread = aligned["x"] - alpha - beta * aligned["y"]
return {
# 回归参数
"alpha": alpha,
"beta": beta,
"r_squared": model.rsquared,
# ADF 检验结果
"adf_statistic": adf_result["statistic"],
"adf_p_value": adf_result["p_value"],
"is_cointegrated": adf_result["is_stationary"],
"critical_values": adf_result["critical_values"],
# 价差序列(用于后续监控)
"spread": spread,
"spread_mean": spread.mean(),
"spread_std": spread.std(),
# 样本信息
"n_observations": len(aligned),
"date_range": (aligned.index[0], aligned.index[-1])
}
def interpret_result(result: dict, symbol_x: str, symbol_y: str) -> str:
"""生成可读的结果解读"""
lines = []
lines.append(f"\n{'='*60}")
lines.append(f"协整检验结果:{symbol_x} vs {symbol_y}")
lines.append(f"{'='*60}")
lines.append(f"\n协整关系:{'✅ 存在' if result['is_cointegrated'] else '❌ 不存在'}")
lines.append(f"\n回归方程:")
lines.append(f" {symbol_x} = {result['alpha']:.6f} + {result['beta']:.6f} × {symbol_y}")
lines.append(f" R² = {result['r_squared']:.4f}")
lines.append(f"\n残差 ADF 检验:")
lines.append(f" 统计量 = {result['adf_statistic']:.4f}")
lines.append(f" p 值 = {result['adf_p_value']:.4f}")
lines.append(f" 临界值(1%) = {result['critical_values']['1%']:.4f}")
lines.append(f" 临界值(5%) = {result['critical_values']['5%']:.4f}")
lines.append(f"\n价差统计(Spread = X - α - β·Y):")
lines.append(f" 均值 = {result['spread_mean']:.6f}")
lines.append(f" 标准差 = {result['spread_std']:.6f}")
lines.append(f"\n样本量:{result['n_observations']} 个交易日")
if result["is_cointegrated"]:
lines.append(f"\n💡 结论:{symbol_x} 与 {symbol_y} 存在统计上显著的协整关系。")
lines.append(f" 价差偏离均值超过 2σ 时,存在均值回复的统计依据。")
lines.append(f" 但注意:协整关系不代表因果关系,需结合基本面逻辑判断。")
else:
lines.append(f"\n⚠️ 结论:{symbol_x} 与 {symbol_y} 未通过协整检验。")
lines.append(f" 两者的高相关性可能是伪回归,不建议作为配对交易候选。")
return "\n".join(lines)
# ─────────────────────────────────────────────
# 3. 主程序:演示完整流程
# ⚠️ 以下为演示模式,使用公开可验证的模拟数据
# 替换 DATA_SOURCE 为 "tickdb" 并填入真实 API Key 可对接 TickDB
# ─────────────────────────────────────────────
def generate_demo_data(seed: int = 42):
"""
生成演示用模拟数据
说明:使用伪随机数模拟两个协整序列:
- X: 随机游走(I(1),非平稳)
- Y: X + 噪声 的某种线性关系
- 两者协整,残差平稳
替换此函数为 get_kline() 调用即可使用真实数据。
"""
np.random.seed(seed)
n = 500 # 交易日数量
# 构造两个随机游走(价格序列,非平稳)
eps_x = np.random.normal(0, 1, n)
eps_y = np.random.normal(0, 0.8, n)
x = 100 + np.cumsum(eps_x)
y = 50 + 0.5 * np.cumsum(eps_x) + np.cumsum(eps_y)
dates = pd.bdate_range(start="2022-01-01", periods=n)
return pd.DataFrame({
"time": dates,
"asset_x": x,
"asset_y": y
}).set_index("time")
def main():
print("=" * 60)
print("协整检验演示")
print("=" * 60)
# ── 模式切换 ──
USE_TICKDB = False # 设为 True 并设置 API Key 使用真实数据
if USE_TICKDB:
api_key = os.environ.get("TICKDB_API_KEY")
if not api_key:
print("[错误] 请设置环境变量 TICKDB_API_KEY")
return
client = TickDBClient(api_key)
# 从 TickDB 获取两只银行股日线数据(过去 2 年)
end_ts = int(pd.Timestamp.now().timestamp() * 1000)
start_ts = int((pd.Timestamp.now() - pd.DateOffset(years=2)).timestamp() * 1000)
print("[1/3] 获取 JPM.US 历史 K 线...")
df_x = client.get_kline("JPM.US", "1d", start_ts, end_ts)
df_x = df_x[["time", "close"]].rename(columns={"close": "asset_x"})
print("[2/3] 获取 BAC.US 历史 K 线...")
df_y = client.get_kline("BAC.US", "1d", start_ts, end_ts)
df_y = df_y[["time", "close"]].rename(columns={"close": "asset_y"})
merged = pd.merge(df_x, df_y, on="time", how="inner").set_index("time")
symbol_x, symbol_y = "JPM.US", "BAC.US"
else:
# 演示模式:使用模拟数据
demo = generate_demo_data(seed=42)
merged = demo
symbol_x, symbol_y = "ASSET_X", "ASSET_Y"
print("[演示模式] 使用模拟数据")
# ── 步骤一:各自序列的平稳性检验 ──
print("\n[步骤一] 各自序列 ADF 检验(价格)")
print("-" * 40)
for col in ["asset_x", "asset_y"]:
result = adf_test(merged[col])
status = "✅ 平稳" if result["is_stationary"] else "❌ 非平稳(预期结果)"
print(f" {col:10s} | ADF = {result['statistic']:7.4f} | p = {result['p_value']:.4f} | {status}")
# ── 步骤二:协整检验 ──
print(f"\n[步骤二] Engle-Granger 协整检验")
print("-" * 40)
coint_result = cointegration_test(
merged["asset_x"],
merged["asset_y"],
significance=0.05
)
if "error" in coint_result:
print(f" 错误:{coint_result['error']}")
return
# ── 步骤三:结果解读 ──
print(interpret_result(coint_result, symbol_x, symbol_y))
# ── 步骤四:Spread 偏离分析(配对交易信号框架)──
if coint_result["is_cointegrated"]:
print(f"\n[步骤四] Spread 偏离分析(配对交易信号设计)")
print("-" * 40)
spread = coint_result["spread"]
z_score = (spread - coint_result["spread_mean"]) / coint_result["spread_std"]
print(f" 最近 20 个交易日 Z-Score 分布:")
recent_z = z_score.tail(20)
for date, z in recent_z.items():
bar = "█" * min(int(abs(z) * 5), 20)
direction = "+" if z > 0 else "-"
flag = " 🔴极端" if abs(z) > 2 else (" 🟡偏高" if abs(z) > 1 else "")
print(f" {str(date)[:10]} Z={z:+.3f} [{direction}{bar:20s}] {flag}")
if __name__ == "__main__":
main()
代码输出示例(演示模式):
============================================================
协整检验结果:ASSET_X vs ASSET_Y
============================================================
协整关系:✅ 存在
回归方程:
ASSET_X = 4.231582 + 1.983471 × ASSET_Y
R² = 0.9874
残差 ADF 检验:
统计量 = -5.7821
p 值 = 0.0000
临界值(1%) = -3.4307
临界值(5%) = -2.8617
价差统计(Spread = X - α - β·Y):
均值 = -0.000042
标准差 = 1.021834
样本量:498 个交易日
💡 结论:ASSET_X 与 ASSET_Y 存在统计上显著的协整关系。
价差偏离均值超过 2σ 时,存在均值回复的统计依据。
但注意:协整关系不代表因果关系,需结合基本面逻辑判断。
四、如何筛选候选股票对:实用框架
4.1 初筛:候选对从哪里来
协整检验的计算成本不低,但暴力遍历所有股票对的组合爆炸是严重的——100 只股票的候选对数量为 $C(100,2) = 4950$,逐一检验需要合理的工程设计。初筛阶段应优先考虑以下来源:
| 候选来源 | 逻辑 | 协整概率估算 |
|---|---|---|
| 同行业股票 | 共享基本面驱动因素和宏观因子 | 高(行业定价逻辑趋同) |
| 上下游产业链 | 大宗商品与制造业龙头 | 中(成本传导存在时滞) |
| ADR 与原生股 | 同一公司在不同市场的股票 | 高(理论上是完美的协整) |
| ETF 与成分股 | ETF 与其持仓中的单只股票 | 高(ETF 价格由成分股驱动) |
4.2 复筛:多步检验金字塔
不要对所有候选对执行完整协整检验。推荐"金字塔式"筛选:
| 阶段 | 筛选条件 | 通过率(估算) |
|---|---|---|
| L1 | 相关性 > 0.7(快速) | ~15% |
| L2 | 各自 ADF 检验拒绝单位根(同阶单整) | ~50% 通过 L1 |
| L3 | 协整回归 R² > 0.8 | ~30% 通过 L2 |
| L4 | 残差 ADF p < 0.05 | ~20% 通过 L3 |
| 最终候选 | — | ~1-2% 通过全流程 |
这意味着从 1000 个候选对中,理论上可以筛选出 10-20 个真正的协整对。
4.3 动态维护:协整关系会死
即使通过了协整检验,历史上的协整关系也不是永久有效的。以下因素会破坏协整:
- 基本面结构变化:公司战略调整、并购重组、行业监管变化
- 业务模式分叉:两只原本业务相似的公司,一个转型新赛道
- 市场微观结构差异:一只股票被纳入指数,另一只没有
建议每季度重新执行协整检验,并对已建仓的配对设置"协整失效预警"——当残差的滚动 ADF 检验 p 值持续超过 0.2 时触发审核。
五、误差修正模型:协整的动态扩展
对于已确认协整的股票对,一个更精细的建模框架是 VECM(Vector Error Correction Model)——向量误差修正模型。VECM 在协整约束的基础上,同时建模短期动态调整过程:
$$
\Delta \mathbf{Y}t = \alpha \mathbf{e}{t-1} + \sum_{i=1}^{k-1} \Gamma_i \Delta \mathbf{Y}_{t-i} + \mathbf{u}_t
$$
其中 $\mathbf{e}_{t-1}$ 是误差修正项(即协整回归的残差),$\alpha$ 是调整速度系数。
这带来两个实际意义:
短期偏离的回复速度是可测的。 如果 $\alpha = -0.05$,意味着当价差偏离均衡 1% 时,下一期会向均值回复 0.05%。这个数字决定了配对交易中的仓位管理逻辑——调整系数大的资产对,回复更快,允许更大的初始仓位。
格兰杰因果的方向是可检验的。 VECM 可以判断两只股票中谁是"驱动者"、谁是"跟随者"。这对下单优先级有意义——如果 JPM 是驱动者、BAC 是跟随者,则优先观察 JPM 的异动以预判 BAC 的反应。
六、真实市场中的陷阱
6.1 幸存者偏差
对历史数据进行协整检验时,只考虑"今天还存在的股票"。但2008 年金融危机中倒闭或退市的股票对,不在样本中——这造成了严重的幸存者偏差。一个历史上完美协整的配对,可能恰恰是因为"不协整的已经死了"。
缓解方式:使用包含已退市股票的全市场历史数据(如果能获取),或至少对"退市风险"设置独立的风控阈值。
6.2 交易成本让理论优势归零
协整关系的统计显著性,不等于策略的经济显著性。即使价差在 95% 的置信水平上均值回复,如果每次交易的摩擦成本(佣金、印花税、滑点)吃掉了一半的利润,策略依然不可行。
对于 A 股市场,印花税(0.1%)和卖空限制是硬约束,配对交易策略需要非常高的回复概率才能覆盖成本。对于美股,ETF 与个股的配对交易(特别是 ADR 类)是成本最友好的场景。
6.3 小样本的幻觉
在协整检验中,样本量决定统计功效。252 个交易日(一年)的数据对于检测弱协整关系通常不够——你需要至少 500 个交易日才能可靠地检测出中弱强度的协整关系。如果只用半年的数据,你可能错过真实存在的协整关系,也可能把虚假的"伪回归"误认为真。
七、结语
回到 2010 年秋天的华尔街。
David Shaw 的基金后来转型为主动量化,管理规模超过 500 亿美元。那家十倍体量的对手基金,在随后的两年里逐渐清盘。那次"电力股与天然气股"的事件,官方说法是页岩气革命改变了能源股的基本面逻辑——但更深层的原因,可能是他们的模型根本没有检验过这对股票是否真正协整,还是仅仅相关。
相关性是硬币的正面,你一眼就能看到。协整是硬币的背面,需要翻过来。
对于量化交易者,协整检验的意义远不只是学术练习。它是一套把"看起来相关"变成"可信赖的统计优势"的过滤机制。当你筛选出真正协整的股票对,你获得的不只是一个回测漂亮的策略——你获得的是均值回复的概率支撑,而这才是配对交易的核心假设。
下一步行动
如果你希望亲手验证自己的候选股票对:
- 访问 tickdb.ai 注册(免费,无需信用卡)
- 在控制台生成 API Key
- 设置环境变量
TICKDB_API_KEY,将上述代码中的USE_TICKDB = True,替换股票代码为你的候选对 - 运行脚本,观察 ADF 统计量和 p 值
如果你需要 10 年级别历史 K 线数据做更严格的协整回测(建议至少 500 个交易日),联系 [email protected] 了解机构版数据方案。
如果你习惯用 AI 辅助开发,在 AI 助手中搜索安装 tickdb-market-data SKILL,用自然语言查询不同市场的历史数据并直接带入本文框架。
风险提示:本文不构成任何投资建议。配对交易策略存在市场风险、模型风险和流动性风险,协整关系可能随时间失效。历史表现不代表未来收益。