金融时间序列基础:股票价格与收益率、对数收益率计算、数据可视化与平稳性检验
各位同学,欢迎来到第二章。
上一章我们聊了强化学习的基本框架。从这章开始,我们要真正接触金融数据了。你想想看,量化交易的核心是什么?是数据。没有扎实的数据处理功底,再厉害的强化学习模型也是空中楼阁。
我个人习惯,做任何量化策略之前,先花 70% 的时间在数据清洗和特征工程上。这章的内容,就是为后续的强化学习模型打地基。地基稳不稳,直接决定了你策略的上限。
2.1 股票价格数据:原始素材长什么样?
我们先看看最原始的股票价格数据。通常我们从数据源拿到的,是 OHLCV 数据:Open(开盘价)、High(最高价)、Low(最低价)、Close(收盘价)、Volume(成交量)。
这里有个细节,我刚开始做量化时踩过坑——复权问题。股票会分红、送股,导致价格出现跳空。如果你直接用原始价格做回测,结果会失真。我建议统一使用后复权价格,这样能真实反映持有期的收益。
核心要点:价格本身是非平稳的,不能直接用于建模。我们真正需要的是收益率。
2.2 收益率计算:简单收益率 vs 对数收益率
为什么要算收益率?因为价格有趋势,收益率相对稳定。我们来看两种最常见的计算方式。
2.2.1 简单收益率
公式很简单:
R_t = (P_t - P_{t-1}) / P_{t-1}
这表示你今天相对于昨天赚了或亏了多少百分比。直观,好理解。但有个问题——它不对称。举个例子:先涨 50% 再跌 50%,你亏了 25%,而不是回到原点。
2.2.2 对数收益率
我个人更偏爱对数收益率:
r_t = ln(P_t / P_{t-1})
为什么?因为对数收益率具有可加性。多期的对数收益率可以直接相加得到总收益率。这在金融建模中非常方便。而且,对数收益率的分布更接近正态分布,很多统计模型都基于这个假设。
我的经验:在强化学习的状态空间中,我几乎只用对数收益率。它让模型更容易学习到稳定的模式。
2.3 实战代码:从价格到收益率
光说不练假把式。我们直接上代码。假设你已经有了一个包含收盘价的 DataFrame。
import pandas as pd
import numpy as np
# 假设 df 包含 'Close' 列
df['simple_return'] = df['Close'].pct_change()
df['log_return'] = np.log(df['Close'] / df['Close'].shift(1))
# 去掉第一行的 NaN
df = df.dropna()
print(df[['Close', 'simple_return', 'log_return']].head())
嗯,这里要注意:pct_change() 默认计算的是简单收益率。对数收益率需要手动用 np.log 计算。我曾经在项目里直接拿简单收益率当对数收益率用,结果模型训练出来一塌糊涂。后来排查了半天才发现是这里的问题。
2.4 数据可视化:用眼睛看趋势
数据算出来了,我们得看看长什么样。可视化是量化分析的第一步,也是最重要的一步。我习惯先画价格走势,再看收益率分布。
import matplotlib.pyplot as plt
fig, axes = plt.subplots(3, 1, figsize=(12, 10))
# 价格走势
axes[0].plot(df['Close'], color='blue')
axes[0].set_title('股票收盘价走势')
axes[0].set_ylabel('价格')
# 简单收益率
axes[1].plot(df['simple_return'], color='green', alpha=0.6)
axes[1].set_title('简单收益率')
axes[1].set_ylabel('收益率')
# 对数收益率
axes[2].plot(df['log_return'], color='red', alpha=0.6)
axes[2].set_title('对数收益率')
axes[2].set_ylabel('对数收益率')
plt.tight_layout()
plt.show()
你仔细看,价格图有明显的趋势,而收益率图看起来像「白噪声」——围绕 0 上下波动。这就是我们想要的。收益率序列相对平稳,适合作为强化学习模型的输入。
避坑指南:我曾经直接用价格序列训练模型,结果模型学会了「一直做多」这种无意义的策略。因为价格有上涨趋势,模型发现做多就能赚钱,完全忽略了风险。改用收益率后,模型才开始真正学习买卖时机。
2.5 平稳性检验:你的数据合格吗?
为什么强调平稳性?因为大多数统计模型和机器学习模型都假设数据是平稳的。平稳性意味着数据的统计性质(均值、方差)不随时间变化。
检验平稳性最常用的方法是 ADF 检验(Augmented Dickey-Fuller test)。
from statsmodels.tsa.stattools import adfuller
# 对收盘价做 ADF 检验
result_price = adfuller(df['Close'])
print(f'价格序列 ADF 统计量: {result_price[0]:.4f}')
print(f'p-value: {result_price[1]:.4f}')
# 对对数收益率做 ADF 检验
result_return = adfuller(df['log_return'])
print(f'对数收益率 ADF 统计量: {result_return[0]:.4f}')
print(f'p-value: {result_return[1]:.4f}')
结果通常是这样:
| 序列 | ADF 统计量 | p-value | 是否平稳 |
|---|---|---|---|
| 收盘价 | -1.23 | 0.65 | 否(p > 0.05) |
| 对数收益率 | -15.67 | 0.00 | 是(p < 0.05) |
你看,价格序列的 p-value 很大,说明不平稳。而对数收益率的 p-value 接近 0,说明序列平稳。这就是为什么我们一定要用收益率。
小技巧:如果 ADF 检验显示不平稳,可以试试一阶差分。但说实话,在金融时间序列里,对数收益率已经足够好了。我很少再做额外的差分处理。
2.6 本章知识体系
为了让你更直观地理解本章的知识结构,我画了一张流程图。
这张图清晰地展示了从原始数据到可用特征的完整流程。每一步都不可或缺。跳过任何一步,你的模型都可能学到错误的东西。
2.7 本章小结
好了,这一章的内容就到这里。我们讲了价格与收益率的区别,学会了计算对数收益率,用可视化验证了数据的特性,最后用 ADF 检验确认了平稳性。
这些基础操作,在后续的强化学习建模中会反复用到。你想想看,如果连输入数据都是错的,模型怎么可能做出正确的交易决策?
下一章,我们会把这些处理好的数据,真正用到强化学习的环境构建中。到时候你会发现,今天打的地基有多重要。
核心记忆点:永远用对数收益率,永远做平稳性检验。这是量化交易的铁律。
公众号:蓝海数据掘金营,微信deep3321