Week 13:时间序列基础、收益率与滚动统计

公開日: 2026-07-26 13:00 4591文字 23 min read

この投稿は「日本語」では表示できません。元の投稿を表示しています。
Week 13:时间序列基础、收益率与滚动统计。fish-first 终端教学,面向 CachyOS、VS Code、uv 和 Python 学习路线。
Oh My Pi / weekly tutorial / week13-time-series-basics
miku@cachyos:~/Code/python-learning$ omp teach week13-time-series-basics --fish-first --step-by-step
source520 行教学文档
weekWeek 13
shellfish-first 命令版
backlink20 周计划

Week 13:时间序列基础、收益率与滚动统计

返回总计划:USTC 统计 + AI / 量化 20 周成长计划

本周目标不是马上预测股价,而是先学会把价格序列变成可以分析的时间序列数据:收益率、滚动均值、滚动波动率、自相关、平稳性直觉,以及最基础的画图检查。

本文命令默认使用 fish shell。如果你正在 CachyOS 的终端里跟做,虚拟环境激活命令必须使用 fish 版本:

ompfish
source .venv/bin/activate.fish

不要把 Bash / Zsh 的激活脚本直接拿到 fish 里执行。

0. 本周详细教学:语法、规范、验收

本节不是追加在尾部的复习,而是本周正文的入口。先读这里,再做后面的命令和项目。

0.1 本周真正要学会什么

维度要求
知识点return、rolling、autocorrelation、时间索引
代码语法能从空文件写出本周核心脚本,而不是只复制运行
程序规范函数拆分、路径清楚、输入输出明确、错误能解释
交付物notebooks/week13_time_series_intro.ipynb
验收方式从 fish 终端运行命令,得到可复查的文件或指标

0.2 代码语法精讲

下面的代码不是最终答案,而是本周必须理解的最小骨架:

omppython
df = df.sort_values("date")
df["ret"] = df["close"].pct_change()
df["log_ret"] = np.log(df["close"]).diff()
df["vol_20"] = df["ret"].rolling(20).std()
df["mom_20"] = df["close"].pct_change(20)
print(df.tail())

读代码时按四步检查:输入从哪里来;中间变量的类型和 shape 是什么;函数或脚本输出什么;哪些错误应该显式报出来。

0.3 本周程序规范

  • 所有路径用相对路径或 `pathlib.Path`,不要写死 `/home/miku/...`。
  • 核心逻辑进 `src/`,notebook 只做探索和解释。
  • 每个脚本能从 fish 终端运行,并在 README 写出命令。
  • 输出必须落盘到 `reports/`、`figures/` 或 `outputs/`,不能只在屏幕上看。

0.4 本周练习分层

层级任务不合格表现合格验收
最小练习手写上面的最小骨架只在 notebook 里运行终端运行成功
标准练习把逻辑拆成函数/模块一个大脚本从头写到尾至少 2 个函数,职责清楚
项目练习生成本周交付物 notebooks/week13_time_series_intro.ipynb只有屏幕输出文件落盘,可复查
复盘练习写 3 个错误和修复只写“已解决”写清报错、原因、修复、预防

0.5 本周和主线的连接

1. 本周学习目标

完成本周后,你应该能回答这些问题:

  1. 为什么量化里通常分析收益率,而不是直接分析价格?
  2. 简单收益率和对数收益率有什么区别?
  3. rolling mean / rolling volatility 在看什么?
  4. 什么叫“时间序列不平稳”,为什么金融价格经常不适合直接建模?
  5. 如何用图和几个简单统计量检查一条序列?

本周交付物建议为:

ompprompt
week13-time-series-basics/
├── data/
│   └── prices.csv
├── figures/
│   ├── price_and_returns.png
│   ├── rolling_stats.png
│   └── autocorrelation.png
├── notebooks/
│   └── week13_time_series_intro.ipynb
├── reports/
│   └── week13_time_series_notes.md
├── src/
│   └── week13_time_series_basics.py
├── pyproject.toml
└── README.md

如果你还不熟悉 notebook,可以先用 src/week13_time_series_basics.py 写脚本;等脚本跑通后,再把关键图和解释整理到 notebook 或报告里。

2. 前置条件

你需要已经具备:

  • CachyOS / Arch 系 Linux 的基本终端操作。
  • VS Code 能打开项目目录。
  • uv 已安装。
  • Python 虚拟环境能正常创建和激活。
  • 已经学过 pandasnumpymatplotlib 的基础用法。
  • 能理解均值、方差、标准差、相关系数这些统计概念。

先进入统一学习目录:

ompfish
mkdir -p ~/Code/ustc-stat-ai-quant
cd ~/Code/ustc-stat-ai-quant

解释:

命令 含义
mkdir -p 创建目录;目录已存在时不报错
~/Code/ustc-stat-ai-quant 建议保存 20 周项目的总目录
cd 进入该目录,后续命令都在这里执行

3. 创建 Week 13 项目

ompfish
mkdir week13-time-series-basics
cd week13-time-series-basics
uv init
uv venv
source .venv/bin/activate.fish
uv add pandas numpy matplotlib statsmodels jupyter ipykernel
mkdir -p data src notebooks reports figures
code .

逐句解释:

命令 作用
mkdir week13-time-series-basics 为本周单独建项目,不和其它周混在一起
uv init 生成 pyproject.tomlREADME.md 等项目文件
uv venv 创建当前项目专用的 .venv
source .venv/bin/activate.fish 在 fish shell 中激活虚拟环境
uv add ... 安装数据分析、画图、时间序列相关依赖
mkdir -p ... 创建数据、代码、报告、图片目录
code . 用 VS Code 打开当前项目

检查 Python 是否来自当前项目:

ompfish
which python
python --version
python -c "import pandas as pd; import numpy as np; import matplotlib; print('ok')"

如果 which python 的输出包含 .venv/bin/python,说明环境正确。

4. 准备一份练习价格数据

真实市场数据可以以后再接入。本周先生成一份模拟价格数据,重点练概念和代码流程。

在 VS Code 中创建 src/make_sample_prices.py,写入下面代码:

omppython
from pathlib import Path

import numpy as np import pandas as pd

rng = np.random.default_rng(42) dates = pd.bdate_range(“2023-01-02”, periods=260)

模拟日对数收益:小正漂移 + 随机波动 + 少数冲击日

log_returns = rng.normal(loc=0.0004, scale=0.012, size=len(dates)) shock_idx = rng.choice(len(dates), size=6, replace=False) log_returns[shock_idx] += rng.normal(loc=-0.025, scale=0.02, size=len(shock_idx))

price = 100 * np.exp(np.cumsum(log_returns))

out = pd.DataFrame({“date”: dates, “close”: price}) Path(“data”).mkdir(exist_ok=True) out.to_csv(“data/prices.csv”, index=False) print(out.head()) print(“saved to data/prices.csv”)

运行:

ompfish
python src/make_sample_prices.py

检查文件:

ompfish
python -c "import pandas as pd; df = pd.read_csv('data/prices.csv'); print(df.head()); print(df.tail())"

这里使用的是模拟数据,所以不能拿结论解释真实市场。它的作用是让你安全地练习时间序列处理。

5. 读取价格并设置时间索引

创建 src/week13_time_series_basics.py,先写入读取部分:

omppython
from pathlib import Path

import matplotlib.pyplot as plt import numpy as np import pandas as pd from statsmodels.graphics.tsaplots import plot_acf

FIGURE_DIR = Path(“figures”) FIGURE_DIR.mkdir(exist_ok=True)

prices = pd.read_csv(“data/prices.csv”, parse_dates=[“date”]) prices = prices.sort_values(“date”).set_index(“date”)

print(prices.head()) print(prices.info())

运行:

ompfish
python src/week13_time_series_basics.py

解释:

  • parse_dates=["date"]:把日期列解析成真正的时间类型。
  • sort_values("date"):时间序列必须按时间排序。
  • set_index("date"):把日期设为索引,后面 rolling 和画图更自然。

时间序列第一条规则:先确认时间顺序,再做任何统计。

6. 价格、简单收益率、对数收益率

在脚本中继续加入:

omppython
prices["simple_return"] = prices["close"].pct_change()
prices["log_return"] = np.log(prices["close"]).diff()

print(prices[[“close”, “simple_return”, “log_return”]].head(10)) print(prices[[“simple_return”, “log_return”]].describe())

运行:

ompfish
python src/week13_time_series_basics.py

概念解释:

名称 公式直觉 常见用途
简单收益率 今天价格相对昨天涨跌百分比 回测中计算资产组合收益很直观
对数收益率 价格取对数后做差 数学上可加,时间序列建模时常用

为什么不用价格直接建模?

  • 价格经常有趋势,均值随时间变化。
  • 价格尺度随时间变大,波动范围也可能变化。
  • 很多模型默认数据围绕稳定均值波动,价格往往不满足。
  • 收益率更接近“每天的相对变化”,通常比价格更适合做统计分析。

注意:第一天没有前一天价格,所以收益率是缺失值。这是正常现象,不要强行填 0。

7. 画价格和收益率

继续加入画图代码:

omppython
fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)

prices[“close”].plot(ax=axes[0], title=“Simulated Close Price”) axes[0].set_ylabel(“price”)

prices[“simple_return”].plot(ax=axes[1], title=“Daily Simple Return”) axes[1].axhline(0, color=“black”, linewidth=1) axes[1].set_ylabel(“return”)

fig.tight_layout() fig.savefig(FIGURE_DIR / “price_and_returns.png”, dpi=150) plt.close(fig)

运行并查看图片:

ompfish
python src/week13_time_series_basics.py
xdg-open figures/price_and_returns.png

看图时不要只问“涨了吗”,要问:

  1. 价格是否有明显趋势?
  2. 收益率是否围绕 0 上下波动?
  3. 是否有异常大的正收益或负收益?
  4. 波动是否集中在某些时间段?

金融数据经常不是“均匀随机噪声”,而是会出现波动聚集:一段时间很平静,另一段时间连续剧烈波动。

8. rolling mean 与 rolling volatility

继续加入:

omppython
window = 20
prices["rolling_mean_20"] = prices["simple_return"].rolling(window).mean()
prices["rolling_vol_20"] = prices["simple_return"].rolling(window).std()

fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)

prices[“rolling_mean_20”].plot(ax=axes[0], title=“20-Day Rolling Mean of Returns”) axes[0].axhline(0, color=“black”, linewidth=1) axes[0].set_ylabel(“mean”)

prices[“rolling_vol_20”].plot(ax=axes[1], title=“20-Day Rolling Volatility of Returns”) axes[1].set_ylabel(“volatility”)

fig.tight_layout() fig.savefig(FIGURE_DIR / “rolling_stats.png”, dpi=150) plt.close(fig)

print(prices[[“simple_return”, “rolling_mean_20”, “rolling_vol_20”]].tail())

运行:

ompfish
python src/week13_time_series_basics.py
xdg-open figures/rolling_stats.png

解释:

  • rolling(20).mean():每一天都看过去 20 个交易日的平均收益。
  • rolling(20).std():每一天都看过去 20 个交易日的波动率。
  • 前 19 天没有足够窗口,所以结果是缺失值,这是合理的。

量化里 rolling 指标非常重要,因为策略在某一天做决策时,只能使用这一天之前已经发生的数据。以后做回测时,要特别检查 rolling 特征有没有偷看未来。

9. 平稳性直觉

你不需要本周就精通严谨的单位根检验,但必须先建立直觉。

粗略地说,一条平稳序列应该满足:

ompprompt
均值大致稳定
波动范围大致稳定
自相关结构大致稳定
没有明显长期趋势

价格通常不像平稳序列:

ompprompt
价格可能长期上升或下降
价格水平改变后,均值不再稳定
价格的绝对尺度会影响波动大小

收益率通常更接近平稳,但也不完美:

ompprompt
收益率均值更接近稳定
收益率仍可能有波动聚集
极端事件会破坏简单模型假设

在脚本里加入一个简单比较:

omppython
first_half = prices.iloc[: len(prices) // 2]
second_half = prices.iloc[len(prices) // 2 :]

summary = pd.DataFrame( { “price_first_half”: first_half[“close”].describe(), “price_second_half”: second_half[“close”].describe(), “return_first_half”: first_half[“simple_return”].describe(), “return_second_half”: second_half[“simple_return”].describe(), } ) print(summary)

运行:

ompfish
python src/week13_time_series_basics.py

观察重点:价格两段的均值可能差很多;收益率两段的均值通常更接近,但标准差仍可能变化。

10. 自相关:今天和过去有关吗

自相关回答的是:今天的收益率是否和前几天的收益率有关系。

继续加入:

omppython
returns = prices["simple_return"].dropna()

print(“lag 1 autocorrelation:”, returns.autocorr(lag=1)) print(“lag 5 autocorrelation:”, returns.autocorr(lag=5)) print(“lag 20 autocorrelation:”, returns.autocorr(lag=20))

fig = plot_acf(returns, lags=30) fig.set_size_inches(10, 5) fig.tight_layout() fig.savefig(FIGURE_DIR / “autocorrelation.png”, dpi=150) plt.close(fig)

运行:

ompfish
python src/week13_time_series_basics.py
xdg-open figures/autocorrelation.png

解释:

  • 如果 lag 1 自相关明显为正,可能表示短期动量。
  • 如果 lag 1 自相关明显为负,可能表示短期反转。
  • 如果大部分自相关接近 0,说明简单线性关系很弱。

不要看到一点点相关就兴奋。金融时间序列噪声很大,样本外稳定性比样本内漂亮图更重要。

11. 整理报告

创建 reports/week13_time_series_notes.md,建议包含:

ompprompt
# Week 13 Time Series Notes

Data

  • 数据来源:模拟价格数据
  • 时间范围:2023 年 260 个工作日

What I computed

  • close price
  • simple return
  • log return
  • 20-day rolling mean
  • 20-day rolling volatility
  • autocorrelation

Observations

  • 价格是否有趋势?
  • 收益率是否围绕 0 波动?
  • rolling volatility 是否有明显变化?
  • 自相关是否明显?

Stationarity intuition

  • 为什么价格更不平稳?
  • 为什么收益率更适合建模?

Questions

  • 如果换成真实股票或指数,结果会怎样?

用 VS Code 创建和编辑报告:

ompfish
code reports/week13_time_series_notes.md

12. 文件布局检查

运行:

ompfish
pwd
ls
ls data src reports figures

理想结果类似:

ompprompt
week13-time-series-basics/
data  figures  notebooks  pyproject.toml  README.md  reports  src

你最终至少应有:

ompprompt
data/prices.csv
src/make_sample_prices.py
src/week13_time_series_basics.py
figures/price_and_returns.png
figures/rolling_stats.png
figures/autocorrelation.png
reports/week13_time_series_notes.md

13. 练习

  1. 把 rolling window 从 20 改成 5、60,比较图像差异。
  2. 分别计算简单收益率和对数收益率的均值、标准差、最小值、最大值。
  3. 找出收益率绝对值最大的 5 天,检查这些天在价格图上是否明显。
  4. 把模拟数据的随机种子从 42 改成 7,再生成一次,观察结论是否稳定。
  5. pandas.Series.skew()pandas.Series.kurt() 查看收益率的偏度和峰度。
  6. 试着下载一个真实指数或 ETF 的日线数据,再重复同样分析,并在报告中说明数据来源。

14. 验收检查

在项目根目录执行:

ompfish
source .venv/bin/activate.fish
python src/make_sample_prices.py
python src/week13_time_series_basics.py
python -c "from pathlib import Path; required = ['data/prices.csv', 'figures/price_and_returns.png', 'figures/rolling_stats.png', 'figures/autocorrelation.png', 'reports/week13_time_series_notes.md']; missing = [p for p in required if not Path(p).exists()]; print('missing:', missing); raise SystemExit(1 if missing else 0)"

通过标准:

  • 命令能顺利运行。
  • figures/ 下有三张图片。
  • 报告能解释价格和收益率的差异。
  • 你能口头说明为什么时间序列不能随便随机打乱。

15. 常见错误

错误 1:fish 中激活环境失败

错误原因通常是用了 Bash / Zsh 的激活脚本。

fish 应使用:

ompfish
source .venv/bin/activate.fish

错误 2:收益率第一行是空值

这是正常的,因为第一天没有前一天价格。不要为了“看起来整齐”把它随便改成 0。

错误 3:rolling 前几行是空值

rolling(20) 需要至少 20 个观测值。前 19 行没有完整窗口,所以是缺失值。

错误 4:图保存失败

先确认 figures/ 存在:

ompfish
mkdir -p figures

脚本里也建议写:

omppython
Path("figures").mkdir(exist_ok=True)

错误 5:把价格图看成策略收益

价格上涨不代表策略有效。策略必须明确什么时候买、什么时候卖、交易成本多少、样本外是否有效。这些将在 Week 15 和 Week 16 继续做。

16. 下一步

进入 Week 14:时间序列验证、rolling window 与数据泄露检查。下一周重点不是模型多高级,而是避免把未来信息泄露给过去。

plain

気に入ったならばコメントを残してくださいね~

© 2026 江无没有月 @miku
Powered by theme astro-koharu · Inspired by Shoka