4. 价差统计特征:分布、自相关与平稳性检验
好,咱们进入第四讲。前面我们聊了价差怎么算,怎么清洗。但拿到一堆价差数据后,你第一反应是什么?
我个人习惯,先摸清它的「脾气」。这数据是胖是瘦?有没有记忆性?会不会突然变了个样?
说白了,就是三个核心问题:分布、自相关、平稳性。这三个搞定了,你对价差的性格就了如指掌。
4.1 价差的分布特征
先看分布。价差长什么样?是正态分布吗?
我告诉你,绝大多数情况下不是。我在做美股做市商项目时,发现价差分布往往是右偏的——大部分时间价差很小,偶尔会突然拉大。你想想看,流动性好的时候,价差就一两个tick;遇到新闻事件,瞬间跳上去。
4.1.1 描述性统计
先来点基础的。均值、中位数、标准差、偏度、峰度。这些是基本功。
import numpy as np
import pandas as pd
def spread_statistics(spread_series):
stats = {
'均值': np.mean(spread_series),
'中位数': np.median(spread_series),
'标准差': np.std(spread_series),
'最小值': np.min(spread_series),
'最大值': np.max(spread_series),
'偏度': sp_series.skew(),
'峰度': sp_series.kurtosis()
}
return pd.Series(stats)
# 假设spread是清洗后的价差序列
print(spread_statistics(spread))
4.1.2 分布可视化
光看数字不够,画个图更直观。我习惯用直方图叠加核密度估计。
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(10, 6))
sns.histplot(spread, bins=50, kde=True, color='steelblue')
plt.axvline(spread.mean(), color='red', linestyle='--', label='均值')
plt.axvline(spread.median(), color='green', linestyle='--', label='中位数')
plt.title('价差分布特征')
plt.legend()
plt.show()
嗯,这里要注意。如果均值明显大于中位数,说明右偏严重。我曾经遇到一个品种,均值比中位数大了将近一倍——后来发现是每天开盘前几分钟的价差异常大,把均值拉上去了。
4.2 自相关分析
分布看完了,接下来看自相关。说白了就是:现在的价差和过去的价差有没有关系?
如果有,那就有套利空间。如果没有,那就是随机游走,做市商就舒服了。
4.2.1 自相关函数(ACF)
我一般先看ACF图。横轴是滞后阶数,纵轴是相关系数。
from statsmodels.graphics.tsaplots import plot_acf
plt.figure(figsize=(12, 4))
plot_acf(spread, lags=40, alpha=0.05)
plt.title('价差自相关函数')
plt.show()
我记得有一次,分析某个ETF的价差,ACF到第20阶还有显著的相关性。一开始我以为数据有问题,后来发现是因为那个ETF的做市商报价策略有固定的更新周期。嗯,这其实是个信号。
4.2.2 偏自相关函数(PACF)
ACF看的是总相关性,PACF看的是净相关性——排除中间滞后项的影响后,当前值和某个滞后值的直接关系。
from statsmodels.graphics.tsaplots import plot_pacf
plt.figure(figsize=(12, 4))
plot_pacf(spread, lags=40, alpha=0.05, method='ywm')
plt.title('价差偏自相关函数')
plt.show()
ACF和PACF结合起来,可以初步判断用AR模型还是MA模型。这个后面讲建模时会细说。
4.3 平稳性检验
这是最关键的。平稳性不满足,后面所有建模都是扯淡。
什么叫平稳?简单说:统计性质不随时间变化。均值恒定、方差恒定、自相关结构稳定。
4.3.1 为什么平稳性重要?
你想想看,如果价差的均值一直在漂移,你用什么均值回归策略?今天均值是0.5,明天变成1.0,你的交易信号全错。
我刚开始做量化时,就吃过这个亏。一个品种的价差看起来有回归特性,回测漂亮得很。结果实盘一个月,亏得我怀疑人生。后来一查,那段时间价差均值在缓慢上升,我的策略一直在逆势交易。
4.3.2 ADF检验
最常用的就是Augmented Dickey-Fuller检验。原假设是「存在单位根,即非平稳」。
from statsmodels.tsa.stattools import adfuller
def adf_test(series, title=''):
result = adfuller(series.dropna(), autolag='AIC')
print(f'ADF统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
print(f'临界值:')
for key, value in result[4].items():
print(f' {key}: {value:.4f}')
if result[1] < 0.05:
print('结论: 拒绝原假设,序列平稳')
else:
print('结论: 无法拒绝原假设,序列非平稳')
adf_test(spread, '价差序列')
p值小于0.05,说明平稳。大于0.05,说明有单位根。
4.3.3 KPSS检验
ADF检验有个问题——它的原假设是非平稳。有时候我们想反过来验证,用KPSS检验。原假设是「序列平稳」。
from statsmodels.tsa.stattests import kpss
def kpss_test(series, title=''):
result = kpss(series.dropna(), regression='c')
print(f'KPSS统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
if result[1] < 0.05:
print('结论: 拒绝原假设,序列非平稳')
else:
print('结论: 无法拒绝原假设,序列平稳')
kpss_test(spread, '价差序列')
4.3.4 非平稳怎么办?
如果检验发现价差不平稳,别慌。常见处理方式:
- 一阶差分:对价差取差分,通常能消除趋势
- 去均值:减去滚动均值,处理时变均值
- 分段处理:把数据分成不同市场状态,分别建模
# 一阶差分
spread_diff = spread.diff().dropna()
adf_test(spread_diff, '一阶差分后的价差')
我个人的经验是,大多数流动性好的品种,价差在日内是平稳的。但跨日数据往往不平稳——因为市场微观结构在变。所以做高频策略,用日内数据;做低频策略,要格外小心。
4.4 知识体系总览
来,我把这一章的核心逻辑画成一张图。你看一眼就明白了。
这张图把咱们这一章的内容串起来了。从价差序列出发,三个方向:分布特征告诉你数据长什么样,自相关告诉你数据有没有记忆,平稳性检验告诉你数据能不能用传统时间序列模型。
三者缺一不可。我见过太多人,只看分布不看平稳性,或者只看自相关不看分布。结果模型建出来,一跑就崩。
好了,这一章就到这里。把代码跑一遍,看看你手里的价差数据是什么性格。下一章咱们聊怎么用这些特征来建模预测。