Epps 效应与高频相关性:为什么采样越细相关性越假
直觉告诉你:数据越密、频率越高,估计应该越准。但相关性在高频世界里恰恰相反——同一对资产,用日线算相关是 0.6,切到 1 分钟变 0.3,切到 1 秒几乎归零。这就是 Epps 效应:不是市场关系变了,是异步成交把相关性『稀释』掉了。两只股票的 tick 从不落在同一时刻,你为了对齐用旧价前向填充,就在每个细格点里塞进了大量人造的零收益,相关性被这些零拖向 0。本文用纯 numpy 合成两条 latent 相关 0.6 的高频价格路径,泊松稀疏成交 + last-tick 填充完整复现 Epps 塌陷曲线(1 秒 ρ≈0.03、30 分钟才恢复到 0.59);量化流动性对衰减速度的影响(成交越稀疏塌陷越狠);最后给出解药 Hayashi-Yoshida 估计量——用重叠区间累加协方差,不需要同步、不丢数据,在异步稀疏数据下几乎无偏。附完整 Python 与四类真实陷阱。
先给一个会让很多人愣住的事实:同一对股票,你用日线数据算相关系数是 0.6,切到 1 分钟线变成 0.3,再切到 1 秒钟几乎归零。
数据没变,市场关系没变,采样频率变了而已。按常识,频率越高、样本越多,估计应该越准才对。可相关性偏偏反着来——越往高频看,两只资产的相关性越向 0 塌陷。
这个现象 1979 年被 Thomas Epps 发现,此后被反复验证,叫 Epps 效应(Epps Effect)。它不是一个学术玩具:任何做统计套利、配对交易、组合风险、高频对冲的人,只要用高频数据估协方差矩阵,就会一头撞进这个坑——你以为你算的是资产间的真实联动,其实你算的是一堆被采样噪声稀释后的幻觉。
结论先放这:Epps 效应的根源不是市场,是异步成交(asynchronous trading)。 两只股票的成交从不落在同一时刻,你为了把它们对齐到同一个时间网格,用了最自然的办法——前向填充旧价。就是这个填充,在每个细格点里塞进了大量人造的零收益,把相关性拖向 0。本文从零把这套机制和代码走一遍,最后给解药。
先看现象:采样越细,相关性越假#
我先造一个”上帝视角”下确定相关的世界:两条连续的 latent 对数价格,用相关系数 0.6 的布朗运动驱动。然后模拟真实市场里发生的事——成交是离散、稀疏、异步的,你只能在成交发生的时刻看到价格,其余时间靠上一笔成交的旧价撑着。
import numpy as np
rng = np.random.default_rng(2026)
T = 6.5 * 3600 # 一个交易日 6.5 小时(秒)
n = int(T) # latent 过程以 1 秒推进
rho_true = 0.6
sigma1 = 0.20 / np.sqrt(n) # 日波动约 20%
sigma2 = 0.25 / np.sqrt(n)
# 相关的连续 latent 对数收益
z = rng.standard_normal((n, 2))
z[:, 1] = rho_true * z[:, 0] + np.sqrt(1 - rho_true**2) * z[:, 1]
la = np.cumsum(sigma1 * z[:, 0]) # 股票 A 的 latent 对数价格
lb = np.cumsum(sigma2 * z[:, 1]) # 股票 Bpython注意:la 和 lb 是连续的、每秒都存在的真实价格。它们之间的相关性铁定是 0.6,这是我造出来的。现在模拟观测:每秒以某个概率发生一笔成交,没成交的时刻用 last-tick 前向填充。
def ffill(x):
out = x.copy(); last = np.nan
for i in range(len(out)):
out[i] = last if np.isnan(out[i]) else out[i]
if not np.isnan(x[i]): last = x[i]
return out
def observed_corr(la, lb, intensity, step):
# 泊松稀疏成交 + last-tick 前向填充 + 固定网格采样
obs_a = np.where(rng.random(n) < intensity, la, np.nan)
obs_b = np.where(rng.random(n) < intensity, lb, np.nan)
fa, fb = ffill(obs_a), ffill(obs_b)
idx = np.arange(0, n, step)
ra, rb = np.diff(fa[idx]), np.diff(fb[idx])
mask = ~(np.isnan(ra) | np.isnan(rb))
ra, rb = ra[mask], rb[mask]
return np.corrcoef(ra, rb)[0, 1]python对不同采样间隔(1 秒到 30 分钟)各跑 30 次蒙特卡洛取平均,画出观测相关性随采样频率的变化:

结果触目惊心:
- 1 秒采样:ρ ≈ 0.03。 真实相关 0.6,观测到的几乎是 0。在最高频,你完全看不到两只资产的联动。
- 1 分钟采样:ρ ≈ 0.5。 恢复了一大半。
- 30 分钟采样:ρ ≈ 0.59。 基本回到真值 0.6。
同一份数据、同一对资产,仅仅因为采样间隔不同,相关系数从 0.03 走到 0.59。这不是估计误差,是系统性偏差——而且方向永远是把相关性往 0 压。
为什么会这样:异步 + 填充 = 人造零收益#
Epps 效应的机制其实很朴素,看懂这张图就懂了:

两只股票的成交(上蓝下红的竖线)落在完全不同的时刻。现在你要算 10 秒收益的相关性,就得把两条序列都对齐到 10 秒格点上。问题来了:某个 10 秒窗口里,可能只有 A 成交了、B 没成交。
B 没成交怎么办?最常用的做法是 last-tick 填充——用 B 上一笔成交的价格。于是 B 在这个窗口的”收益”是 0(价格没动,因为你根本没有新价格)。而 A 那边有真实成交,收益不是 0。
一边真实波动、一边人造零收益,这一对配下来对协方差的贡献就是 0。 采样越细,每个格点里”只有一边成交”的情况越多,人造零收益越多,相关性被稀释得越狠。这就是为什么高频端相关性塌向 0。
反过来,采样越粗(比如 30 分钟一格),窗口足够长,两只股票在窗口内几乎必然都成交过,收益都是真实的,相关性自然恢复。
一句话总结机制:Epps 效应 = 异步成交 × 强行同步对齐。 两个条件缺一不可。连续市场里不存在这个问题,是”离散成交”和”你想要同步数据”这对矛盾造出来的。
流动性是决定塌陷速度的关键#
既然根源是”细格点里一边没成交”,那成交越频繁(流动性越好),这种情况越少,Epps 衰减就越轻。反过来,冷门股、小盘股成交稀疏,Epps 效应会狠得多。
我把成交强度设成高(0.5 笔/秒)、中(0.1 笔/秒)、低(0.02 笔/秒)三档,看衰减曲线:

- 高流动性:即使 1 秒采样,相关性也还能保住相当一部分,30 秒左右就基本恢复。
- 低流动性:1 秒采样相关性趴在地上,得拉到 5~10 分钟才勉强恢复到真值附近。
实务含义很直接:你估一个组合的协方差矩阵,如果里面既有茅台这种流动性巨兽、又有某只日均成交额几千万的小盘股,用同一个采样频率去算,两者的 Epps 偏差程度完全不同。流动性差的资产相关性被低估得更厉害,你的协方差矩阵会系统性地低估它和别人的联动——风险模型据此会告诉你”这个组合很分散”,实际崩盘时它们一起跌,因为真实相关根本没那么低。
解药:Hayashi-Yoshida 估计量#
Epps 效应的病根是”强行把异步数据塞进同步网格”。那最直接的解药就是:别对齐了,直接在异步数据上算协方差。
2005 年 Hayashi 和 Yoshida 给出了一个漂亮的估计量(HY estimator)。思想一句话:A 的每一段收益,乘以所有和它时间上有重叠的 B 收益段,全部累加。 不需要同步,不需要填充,不丢任何一笔成交。
def hy_covariance(times_a, ret_a, times_b, ret_b):
# times_a/b 是成交时刻,ret_a/b 是相邻成交间的对数收益
ta0, ta1 = times_a[:-1], times_a[1:] # 每段收益的起止时刻
tb0, tb1 = times_b[:-1], times_b[1:]
cov = 0.0
for i in range(len(ret_a)):
# 找出所有与 A 第 i 段时间重叠的 B 段
overlap = (tb0 < ta1[i]) & (tb1 > ta0[i])
cov += ret_a[i] * ret_b[overlap].sum()
return cov
def hy_corr(ta, ra, tb, rb):
cov = hy_covariance(ta, ra, tb, rb)
va, vb = (ra**2).sum(), (rb**2).sum() # HY 下方差就是收益平方和
return cov / np.sqrt(va * vb)python关键在那行 overlap:它不要求 A、B 的成交时刻对齐,只要两段收益在时间轴上有交集就相乘累加。这样既不制造人造零收益,也不丢弃任何真实成交。理论上可以证明,HY 估计量在异步采样下对真实积分协方差是无偏、相合的。
对比一下 1 秒固定网格 last-tick 和 HY 在三种流动性下的表现:

固定网格(灰)在低流动性下被 Epps 咬得只剩一点点,HY(绿)三种流动性下都稳稳贴着真值 0.6。这就是为什么严肃的高频协方差估计基本都用 HY 或它的变体,而不是把 tick 数据重采样到固定网格再算 corrcoef。
四类真实陷阱(进阶)#
复现出漂亮曲线只是第一步。真拿这套东西去做研究,下面四个坑一个都躲不开。
陷阱一:把 Epps 偏差误读成”这对资产不相关”。 这是最致命的。你在高频看到相关性接近 0,很容易得出”这两只股票没关系、可以互相对冲/分散”的结论。错。它们的真实相关可能是 0.6,你只是用错了尺度。任何基于高频相关性的配对筛选、风险分散判断,都必须先排除 Epps 偏差——最简单的自检:把采样频率从细往粗扫一遍,如果相关性随尺度单调上升,那你看到的高频低相关十有八九是 Epps 幻觉而不是真实关系。
陷阱二:HY 估计量对时钟误差和异常 tick 极其敏感。 HY 靠”时间重叠”判定哪些收益段相乘,这意味着它高度依赖时间戳的准确性。交易所时钟不同步、数据源打时间戳的延迟、乱序 tick,都会让”重叠”判断出错。更糟的是错单/胖手指造成的价格跳变,会被 HY 原样吸进协方差里。上 HY 之前必须先做 tick 清洗:剔除明显离群的成交价、处理时间戳乱序、对齐不同数据源的时钟基准。脏数据喂进 HY,出来的是精确的垃圾。
陷阱三:微观结构噪声会把方差(对角线)顶上天。 Epps 效应压的是协方差(非对角线),但同一批高频数据里还藏着另一个反向的偏差:买卖价差跳动(bid-ask bounce)会让已实现方差在超高频端被严重高估——价格在买价卖价之间来回弹,制造出大量根本不存在的”波动”。所以你在估协方差矩阵时是两面受敌:对角线被微观结构噪声顶高、非对角线被 Epps 压低,整个矩阵的病态程度(condition number)被双向恶化,做组合优化时会放大成灾难性的权重。实务上常用的折中是”最优采样频率”(比如 5 分钟)或带噪声修正的估计量(TSRV、pre-averaging),而不是无脑上最高频。
陷阱四:多资产下 HY 不保证协方差矩阵半正定。 单对资产用 HY 很干净,但你把 N 个资产两两算 HY 拼成一个 N×N 矩阵时,这个矩阵可能不是半正定的——因为每一对用的重叠区间不一样,缺乏整体一致性。而组合优化(求最小方差权重、算风险平价)严格要求协方差矩阵半正定,否则会解出荒谬的权重甚至无解。拼完 HY 矩阵必须做一步半正定修复(最近邻 PSD 投影、特征值截断),或者改用天生保证半正定的多元估计量。这一步在教科书里常被略过,实盘里却是硬约束。
一句话总结#
Epps 效应告诉你一件反直觉的事:在高频世界里,“更多数据”不等于”更准估计”。 采样越细,异步成交和强行对齐制造的人造零收益越多,相关性被稀释得越狠,直到看起来两个明明联动的资产毫无关系。
破解它靠两件事:理解偏差的方向(永远把相关性往 0 压,且流动性越差越狠),和用对的工具(Hayashi-Yoshida 直接在异步数据上算,不对齐、不填充、不丢数据)。但 HY 也不是银弹——它换来无偏的代价是对数据质量、时钟精度、矩阵半正定性的一整套新要求。
高频相关性从来不是”把数据切细一点算 corrcoef”这么简单。你面对的不是一个数字,是一个被采样机制、流动性、微观结构噪声共同扭曲的观测量。看懂扭曲的方向,你才知道手里的相关系数到底是信号还是幻觉。