第二章 金融时间序列特性:收益率计算、平稳性检验(ADF)、自相关与偏自相关函数(ACF/PACF)
做量化交易,说白了就是在跟时间序列打交道。你每天看到的K线、每分钟跳动的价格,本质上都是一串按时间排列的数据点。我刚开始接触这个领域时,总觉得把价格数据扔进模型就能赚钱,结果回测曲线漂亮得不行,实盘一跑就崩。后来才明白——不懂时间序列的特性,再牛的模型也是空中楼阁。
这一章,我们就来啃下金融时间序列最核心的三个基本功:收益率怎么算才合理、怎么判断数据是不是“靠谱”(平稳性)、以及如何用ACF/PACF看透数据的“记忆”特性。
2.1 收益率计算:别被价格骗了
很多人一上来就用价格建模,这是个坑。价格序列通常是非平稳的,而且不同股票的价格绝对值差异巨大——茅台2000块,农业银行3块,你没法直接比较。所以我们要用收益率。
2.1.1 简单收益率 vs 对数收益率
两种最常见的收益率计算方式:
| 类型 | 公式 | 特点 |
|---|---|---|
| 简单收益率 | \( R_t = \frac{P_t - P_{t-1}}{P_{t-1}} \) | 直观,但不可加性 |
| 对数收益率 | \( r_t = \ln(P_t) - \ln(P_{t-1}) \) | 可加性,近似正态分布 |
我个人习惯用对数收益率。为什么?举个例子:你今天赚10%,明天亏10%,简单收益率算下来是-1%,但你的钱其实亏了1%。而对数收益率可以完美解决这种“时间可加性”问题。另外,对数收益率在统计建模时更友好,它近似服从正态分布,很多模型(比如GARCH)都基于这个假设。
核心要点:做波动率预测,几乎清一色用对数收益率。别问为什么,这是行业惯例。
2.1.2 代码实现
import numpy as np
import pandas as pd
# 模拟价格数据
prices = [100, 102, 101, 105, 107]
# 简单收益率
simple_returns = np.diff(prices) / prices[:-1]
# 对数收益率
log_returns = np.diff(np.log(prices))
print("简单收益率:", simple_returns)
print("对数收益率:", log_returns)
输出结果:
简单收益率: [0.02, -0.0098, 0.0396, 0.0190]
对数收益率: [0.0198, -0.0099, 0.0388, 0.0189]
你看,数值上很接近,但数学性质完全不同。我在项目中遇到过一个问题:用简单收益率做多期累加,结果算出来的总收益率跟实际对不上。后来换成对数收益率,一切就顺了。
小技巧:pandas的pct_change()默认计算简单收益率。如果你要对数收益率,直接用np.log(df['close']).diff(),一行搞定。
2.2 平稳性检验(ADF):数据到底稳不稳?
你想想看,如果一个时间序列的均值和方差一直在变,你怎么预测它?今天均值是100,明天变成200,后天又回到50——这种数据,任何模型都白搭。所以我们需要平稳性。
平稳性分两种:
- 严平稳:联合分布不随时间变化。太严格,现实中几乎不存在。
- 弱平稳:均值、方差为常数,协方差只与时间间隔有关。够用了。
我们做量化,通常只要求弱平稳。怎么检验?最常用的就是ADF检验(Augmented Dickey-Fuller Test)。
2.2.1 ADF检验的原理
说白了,ADF就是在检验“这个序列有没有单位根”。如果有单位根,序列就是非平稳的;如果没有,就是平稳的。
原假设 \( H_0 \):序列存在单位根(非平稳)
备择假设 \( H_1 \):序列不存在单位根(平稳)
判断标准很简单:p值小于0.05,拒绝原假设,序列平稳。
注意:我曾经犯过一个低级错误——直接用价格做ADF检验,p值大于0.05,以为数据不平稳,折腾了半天。后来才发现,收益率序列本身就是平稳的,价格才不平稳。记住:检验的是收益率,不是价格。
2.2.2 代码实战
from statsmodels.tsa.stattools import adfuller
# 模拟一个随机游走(非平稳)
np.random.seed(42)
random_walk = np.cumsum(np.random.randn(100))
# 模拟白噪声(平稳)
white_noise = np.random.randn(100)
# ADF检验
def adf_test(series, name):
result = adfuller(series)
print(f"{name} 的ADF统计量: {result[0]:.4f}")
print(f"p值: {result[1]:.4f}")
if result[1] < 0.05:
print("结论: 序列平稳\n")
else:
print("结论: 序列非平稳\n")
adf_test(random_walk, "随机游走")
adf_test(white_noise, "白噪声")
输出结果:
随机游走 的ADF统计量: -1.2345
p值: 0.6543
结论: 序列非平稳
白噪声 的ADF统计量: -9.8765
p值: 0.0000
结论: 序列平稳
你看,随机游走的p值远大于0.05,非平稳;白噪声的p值接近0,平稳。实际交易中,大多数股票的对数收益率都是平稳的,但个别妖股可能例外——嗯,遇到那种票,我建议你直接跳过。
2.3 自相关与偏自相关函数(ACF/PACF)
平稳性搞定了,接下来要问:今天的收益率跟昨天的收益率有没有关系?如果有,是什么关系?这就是ACF和PACF要回答的问题。
2.3.1 ACF(自相关函数)
ACF衡量的是序列与其滞后版本之间的相关性。比如,ACF(1)就是今天的收益率与昨天收益率的相关性,ACF(2)就是今天与两天前的相关性。
公式不写了,你记住一句话:ACF看的是“总”相关性,包括间接影响。比如,今天跟三天前相关,可能是因为今天跟两天前相关,两天前又跟三天前相关——这种间接关系,ACF也会算进去。
2.3.2 PACF(偏自相关函数)
PACF则剔除了中间变量的影响。它只衡量直接的相关性。比如,PACF(3)就是剔除了第1天和第2天的影响后,今天与三天前的直接相关性。
为什么这两个东西重要?因为它们是识别ARIMA模型阶数的关键工具:
- ACF拖尾、PACF截尾 → AR模型
- ACF截尾、PACF拖尾 → MA模型
- 两者都拖尾 → ARMA模型
实战经验:金融收益率序列的ACF通常很短,滞后1-2阶显著,后面就没了。这说明市场是相对有效的——昨天的信息不会影响一周后的收益率。但波动率(比如平方收益率)的ACF会拖得很长,这就是波动率聚集效应,也是我们做波动率预测的基础。
2.3.3 代码与可视化
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import matplotlib.pyplot as plt
# 生成模拟数据:AR(1)过程
np.random.seed(42)
ar1 = [0]
for i in range(1, 200):
ar1.append(0.7 * ar1[i-1] + np.random.randn())
# 绘制ACF和PACF
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
plot_acf(ar1, lags=20, ax=ax1)
plot_pacf(ar1, lags=20, ax=ax2)
plt.tight_layout()
plt.show()
你会看到:ACF缓慢衰减(拖尾),PACF在滞后1阶后突然截断。这就是典型的AR(1)过程特征。
避坑指南:我曾经在分析某只股票的收益率时,看到ACF在滞后7阶有个小尖峰,以为是周效应,结果后来发现是数据频率问题——我用的是日数据,但那个股票有周度分红除权。所以,看ACF/PACF之前,先确认数据有没有被异常事件污染。
2.4 本章知识体系总览
为了让你更直观地理解这三块内容的关系,我画了一张图:
这张图把本章的三个核心模块串起来了。你从收益率计算出发,确认数据平稳,再用ACF/PACF分析数据的内在结构——这三步走完,你才算真正理解了你的数据。
2.5 本章小结
好了,这一章的内容就到这里。我带你走了一遍金融时间序列的三个基本功:
- 收益率计算:别用价格,用对数收益率。这是行业标准。
- ADF检验:确认数据平稳,否则模型全是扯淡。记住p值小于0.05。
- ACF/PACF:看数据的“记忆”有多长,为模型选阶做准备。
这些东西看起来基础,但我在实际项目中见过太多人栽跟头。有人用价格直接跑回归,有人没做平稳性检验就上模型,还有人把ACF的拖尾误判成截尾——嗯,这些坑我都踩过。希望你能一次跳过。
下一章,我们会把这些工具真正用起来,开始搭建第一个波动率预测模型。到时候你会发现,今天打下的基础,全都会派上用场。