第二章:金融数据与预处理
做量化投资,尤其是强化学习训练,第一步就是跟数据打交道。我见过太多人一上来就调模型,结果数据都是脏的,训练出来的策略跟笑话一样。今天我们就聊聊金融数据的获取和清洗,这是整个资产配置模型的基石。
2.1 获取股票/ETF历史数据
数据源的选择,我个人习惯用两个:一是免费的Tushare或AKShare,适合个人研究;二是Wind或Bloomberg,机构用得比较多。这里我以AKShare为例,因为它对国内ETF支持得不错。
import akshare as ak
import pandas as pd
import numpy as np
# 获取沪深300 ETF历史数据
etf_data = ak.fund_etf_hist_em(
symbol="510300",
period="daily",
start_date="20200101",
end_date="20231231",
adjust="qfq"
)
print(etf_data.head())
嗯,这里要注意。前复权(qfq)和后复权(hfq)的选择很关键。我做过一个回测,用后复权数据训练出来的策略,实盘时直接崩了。为什么?因为后复权会改变历史价格,导致强化学习模型学到的是「假趋势」。
2.2 处理缺失值与异常值
金融数据里缺失值很常见。停牌、节假日、数据源故障都会导致NaN。我的处理原则很简单:
- 缺失值少于5%:用前向填充(ffill)或插值法
- 缺失值在5%-20%:用同类ETF的均值填充
- 缺失值超过20%:直接删除该时间段
# 前向填充缺失值
etf_data.fillna(method='ffill', inplace=True)
# 或者用线性插值
etf_data.interpolate(method='linear', inplace=True)
异常值处理更有意思。我记得有一次,某ETF突然出现单日涨幅20%的数据点。一看,原来是数据源把分红日当成了价格跳变。这种异常值如果不处理,强化学习模型会认为「哇,原来一天能赚20%」,然后疯狂追涨。
# IQR法检测异常值
Q1 = etf_data['收盘'].quantile(0.25)
Q3 = etf_data['收盘'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常值
etf_data['异常标记'] = (etf_data['收盘'] < lower_bound) | (etf_data['收盘'] > upper_bound)
2.3 计算收益率与波动率
收益率计算是核心中的核心。强化学习模型需要的是收益率序列,而不是价格序列。为什么?因为价格是非平稳的,收益率相对平稳。
# 计算对数收益率(推荐)
etf_data['log_return'] = np.log(etf_data['收盘'] / etf_data['收盘'].shift(1))
# 计算简单收益率
etf_data['simple_return'] = etf_data['收盘'].pct_change()
我个人习惯用对数收益率。原因很简单:对数收益率具有可加性,而且更接近正态分布。你想想看,如果某天跌了10%,第二天涨了10%,简单收益率算出来是-1%,但对数收益率算出来是0%。哪个更合理?显然是后者。
波动率计算,我一般用滚动窗口法:
# 20日滚动波动率
window = 20
etf_data['volatility'] = etf_data['log_return'].rolling(window=window).std() * np.sqrt(252)
# 或者用EWMA(指数加权移动平均)
etf_data['ewma_vol'] = etf_data['log_return'].ewm(span=window).std() * np.sqrt(252)
2.4 数据标准化与平稳性检验
强化学习对输入数据的尺度非常敏感。如果价格是几百块,收益率是百分之几,模型会天然地更关注价格特征。所以标准化是必须的。
from sklearn.preprocessing import StandardScaler
# 标准化收益率和波动率
scaler = StandardScaler()
features = ['log_return', 'volatility']
etf_data[features] = scaler.fit_transform(etf_data[features])
平稳性检验,说白了就是检查数据有没有「趋势」或「季节性」。如果数据不平稳,强化学习模型学到的规律可能只是「过去一直在涨」,而不是真正的市场逻辑。
from statsmodels.tsa.stattools import adfuller
# ADF检验
result = adfuller(etf_data['log_return'].dropna())
print(f'ADF统计量: {result[0]}')
print(f'p值: {result[1]}')
if result[1] < 0.05:
print('数据平稳,可以用于训练')
else:
print('数据不平稳,需要差分处理')
嗯,这里有个坑。很多初学者以为收益率天然就是平稳的,其实不一定。我遇到过某些ETF在特定时间段内,收益率序列的ADF检验p值高达0.3。后来发现是因为那段时间市场处于极端波动期,收益率序列出现了明显的波动率聚集效应。
知识体系总览
下面这张图是我自己整理的金融数据预处理流程。每次做新项目,我都会先过一遍这个流程,确保数据质量过关。
数据预处理这件事,说白了就是「垃圾进,垃圾出」。你花80%的时间在数据清洗上,一点都不夸张。我见过太多人把时间花在调参上,结果数据本身就有问题。强化学习模型再厉害,也架不住输入数据是错的。
- 数据获取:前复权优先,注意数据源一致性
- 缺失值:ffill + 插值法,超过20%直接删
- 异常值:IQR法比3倍标准差更稳健
- 收益率:对数收益率优于简单收益率
- 波动率:滚动窗口法,年化因子用252
- 标准化:StandardScaler,注意fit_transform的顺序
- 平稳性:ADF检验,p值小于0.05才放心
好了,数据预处理这块就聊到这儿。记住,数据质量决定了模型的天花板。下一章我们会把这些处理好的数据喂给强化学习模型,看看它们到底能学到什么。
公众号:蓝海资料掘金营,微信deep3321