1. 这节究竟解决什么实际问题?¶
1.7 时间序列分析 的具体任务是:检验平稳性、相关结构和差分效果,避免把趋势误当成可预测性。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
会使用百分比和基础代数;建议先完成环境配置。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
示例优先使用仓库固定的日频复权收盘价;它不是实时行情,也不代表完整市场。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 1.7 时间序列分析 的输入、计算步骤、输出和局限;尤其能说明:检验平稳性、相关结构和差分效果,避免把趋势误当成可预测性。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
价格表示资产在特定时点的水平,收益率表示两个时点之间的相对变化。多数跨资产比较使用收益率。
所有公式都先说明分子、分母、单位与时间尺度;年化前必须确认数据频率。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
NumPy/Pandas 帮你算数和对齐索引;统计库帮你估计参数,但不替你解释经济含义。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“检验平稳性、相关结构和差分效果,避免把趋势误当成可预测性。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
把百分比和小数混用、忘记 dropna、把日频波动率直接叫作年化波动率、把相关性当因果。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
概念演示也要避免事后选择样本或只展示支持结论的时间段。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/01_financial_concepts/07_time_series_analysis_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/01_financial_concepts/07_time_series_analysis_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Investments(Bodie, Kane, Marcus)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 08_heavy_tails。本节的概念会成为理解它的输入,而不是孤立的名词。
import pandas as pd
import numpy as np
import yfinance as yf
from statsmodels.tsa.stattools import adfuller
import warnings
warnings.filterwarnings('ignore')
# 获取 AAPL 价格数据以便演示 (SPY可能不好下,换用AAPL)
try:
data = yf.download("AAPL", start="2020-01-01", end="2023-01-01", progress=False)
prices = data['Adj Close'].dropna()
# 1. 检验价格的平稳性
result_price = adfuller(prices)
print(f'ADF P-value for Prices: {result_price[1]:.4f}') # 通常 > 0.05, 无法拒绝非平稳假设
# 2. 计算对数收益率并检验
returns = np.log(prices / prices.shift(1)).dropna()
result_returns = adfuller(returns)
print(f'ADF P-value for Returns: {result_returns[1]:.4f}') # 通常接近 0.0, 拒绝非平稳假设,认为平稳
except Exception as e:
print('网络可能无法顺畅下载 Yahoo Finance 数据,这里是理论演示。')
# 模拟数据演示
np.random.seed(42)
prices = pd.Series(np.cumsum(np.random.normal(0, 1, 1000)) + 100)
returns = np.log(prices / prices.shift(1)).dropna()
print(f'模拟价格 ADF P-value: {adfuller(prices)[1]:.4f}')
print(f'模拟收益率 ADF P-value: {adfuller(returns)[1]:.4f}')
网络可能无法顺畅下载 Yahoo Finance 数据,这里是理论演示。 模拟价格 ADF P-value: 0.7655 模拟收益率 ADF P-value: 0.0000
# 默认使用固定样本,保证课程与 CI 不依赖实时接口。
USE_OFFLINE_DATA = True
if USE_OFFLINE_DATA:
from pathlib import Path
import sys
docs_root = next(parent for parent in [Path.cwd(), *Path.cwd().parents]
if (parent / 'utils' / 'offline_data.py').exists())
sys.path.insert(0, str(docs_root))
from utils.offline_data import configure_yfinance_offline
configure_yfinance_offline(yf)
else:
print('在线下载为扩展步骤;请核对数据字段、时区和下载日期。')
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
2. 自相关与偏自相关 (Autocorrelation & PACF)¶
自相关 (Autocorrelation, ACF) 衡量时间序列当前值与其过去滞后值 (lag) 之间的相关性。
- 动量效应 (Momentum):正自相关(涨了还会涨)。
- 均值回归 (Mean Reversion):负自相关(涨多了会跌)。
偏自相关 (Partial Autocorrelation, PACF) 同样衡量当前值与滞后值的相关性,但它剔除了中间滞后项的影响。例如,滞后3阶的 PACF 给出了在剔除滞后1阶和2阶影响后,当前值与滞后3阶纯粹的相关性。
画出自相关图(Correlogram)是分析时间序列模式的第一步。
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2, 1, figsize=(10, 8))
# 绘制收益率的 ACF
plot_acf(returns, lags=20, ax=ax[0], title="Autocorrelation (ACF) of Returns")
# 绘制收益率的 PACF
plot_pacf(returns, lags=20, ax=ax[1], title="Partial Autocorrelation (PACF)")
plt.tight_layout()
plt.show()
如果 ACF 和 PACF 在某些滞后项显著不为0(超出置信区间阴影),说明存在可以被自回归模型利用的历史模式。然而,由于有效市场假说(EMH),发达市场日频收益率的自相关性通常极弱。
3. AR, MA 与 ARIMA 模型¶
这些是经典的线性参数模型,用于预测未来的时间序列值。
- AR(p) - 自回归模型 (Autoregressive): 假设当前值是过去 p 个滞后值的线性组合加上白噪声。
- MA(q) - 移动平均模型 (Moving Average): 假设当前值是过去 q 个历史预测误差(白噪声)的线性组合。
- ARMA(p, q): 结合 AR 和 MA。
- ARIMA(p, d, q) - 差分自回归移动平均: 'I' 代表积分/差分 (Integrated)。如果序列本身非平稳,可以进行 d 次差分(如计算一次收益率即 d=1)使其平稳,然后再用 ARMA(p, q) 建模。
尽管 ARIMA 在宏观经济预测中很有用,但在高频/日频的金融交易中,简单的线性模型很难捕捉市场复杂的非线性动态,通常作为更复杂机器学习模型的基准(Baseline)。
4. 使用 Python 估计 ARIMA 模型¶
我们可以使用 statsmodels 库来拟合一个 ARIMA 模型。
- ARIMA(p, d, q) 中的三个参数:
- p (AR阶数): 观测滞后项 (Lag observations)。
- d (差分阶数): 原始观测值差分次数 (使时间序列平稳所需减的次数)。
- q (MA阶数): 滑动平均窗口大小。
from statsmodels.tsa.arima.model import ARIMA
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
# 还是使用前面获取的 prices 价格序列
# 注意:真实的日频股票价格通常呈现随机游走,ARIMA 很难有很好的预测。
# 此处主要是展示模型拟合的流程
# 定义 ARIMA(5, 1, 0) - 即一阶差分后使用 5阶自回归
model = ARIMA(prices, order=(5, 1, 0))
results = model.fit()
print(results.summary())
# 查看残差 (Residuals)
# 良好的模型拟合后,其残差应该像白噪声(无明显自相关)
residuals = pd.DataFrame(results.resid[1:]) # 去除第一天的 NaN
fig, ax = plt.subplots(1, 2, figsize=(14, 4))
residuals.plot(title="Residuals", ax=ax[0])
residuals.plot(kind='kde', title='Density', ax=ax[1])
plt.show()
SARIMAX Results
==============================================================================
Dep. Variable: y No. Observations: 1000
Model: ARIMA(5, 1, 0) Log Likelihood -1394.686
Date: Sat, 07 Mar 2026 AIC 2801.372
Time: 02:17:08 BIC 2830.813
Sample: 0 HQIC 2812.562
- 1000
Covariance Type: opg
==============================================================================
coef std err z P>|z| [0.025 0.975]
------------------------------------------------------------------------------
ar.L1 -0.0049 0.032 -0.150 0.881 -0.068 0.059
ar.L2 -0.0004 0.034 -0.013 0.990 -0.066 0.065
ar.L3 0.0153 0.032 0.473 0.636 -0.048 0.079
ar.L4 -0.0536 0.031 -1.713 0.087 -0.115 0.008
ar.L5 0.0256 0.031 0.817 0.414 -0.036 0.087
sigma2 0.9553 0.042 22.628 0.000 0.873 1.038
===================================================================================
Ljung-Box (L1) (Q): 0.00 Jarque-Bera (JB): 3.03
Prob(Q): 0.98 Prob(JB): 0.22
Heteroskedasticity (H): 1.03 Skew: 0.13
Prob(H) (two-sided): 0.76 Kurtosis: 3.08
===================================================================================
Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
模型预测预测未来¶
我们可以利用拟合好的模型向前进行样本外预测 (Out-of-sample forecasting)。
# 预测未来 10 步 (例如 10天)
forecast_steps = 10
forecast = results.forecast(steps=forecast_steps)
# 获取最后几十天的数据用于作图对比
history_to_plot = prices.iloc[-50:]
plt.figure(figsize=(10, 5))
# 获取最后一个索引值(此时它是个整数,比如 499)
last_idx = history_to_plot.index[-1]
# 生成未来的整数索引:从 last_idx + 1 开始,往后推 forecast_steps 个步长
forecast_index = range(last_idx + 1, last_idx + 1 + forecast_steps)
# 绘图(其他不变)
plt.figure(figsize=(10, 5))
plt.plot(history_to_plot.index, history_to_plot.values, label='Historical Prices')
plt.plot(forecast_index, forecast.values, color='red', label='ARIMA Forecast')
plt.title('ARIMA(5,1,0) Out-of-Sample Forecast')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
<Figure size 1000x500 with 0 Axes>
本章结语¶
时间序列分析帮助我们透过数据的表面噪音看清底层结构。
- 平稳性 (ADF检验) 是所有均值回归策略生效的前提。
- 通过 ACF/PACF 图 我们能窥见数据是否具有动量或反转潜质。
- 利用基础的 ARIMA 线性模型 能够捕捉并预测简单的自身过去模式。
本节的预测图只反映当前样本。仅使用单条历史价格序列的模型可能因低信噪比、结构变化和遗漏变量而在样本外失效。
这些时间序列概念将在后续的多因子分析与金融机器学习章节中继续使用,特别是数据切分、特征构造和残差诊断。
练习¶
- 对 SPY 的日收益率绘制 ACF/PACF 图,是否观察到显著的自相关性?这意味着什么?
- 尝试 ARIMA(1,1,1) 和 ARIMA(2,1,2) 两个模型,用 AIC/BIC 值比较哪个更好。
- 对平均 5 日收益率(周度)进行 ADF 检验,与日频结果对比,频率对平稳性有影响吗?
下一节 → ../02_data/01_data_sources.ipynb