4、数据获取与清洗:使用Tushare/AkShare获取数据、缺失值处理、异常值检测、数据标准化
数据获取与清洗,说白了就是量化投资的「原材料处理车间」。你策略再牛,模型再炫,拿到的数据是脏的、缺的、错的,那结果就是垃圾进垃圾出。我刚开始做量化那会儿,就吃过这个亏——回测曲线漂亮得不行,实盘一跑直接崩,查了半天,原来是某只股票的分红数据没复权处理。
嗯,咱们今天就把这块彻底捋清楚。
4.1 数据源选择:Tushare vs AkShare
国内做量化,绕不开这两个库。我个人习惯是:研究阶段用AkShare,生产环境用Tushare。为什么?
| 对比维度 | Tushare | AkShare |
|---|---|---|
| 数据质量 | 高,经过校验 | 中等,部分源不稳定 |
| 获取方式 | 需注册+Token | 免费,直接pip |
| 频率支持 | 日/分钟/ tick | 日/分钟为主 |
| 文档清晰度 | 好,有社区 | 一般,更新快 |
| 适合场景 | 实盘、回测 | 快速验证、研究 |
4.2 用Tushare获取数据
先注册拿到token,然后安装:
pip install tushare
获取日线数据,我一般这么写:
import tushare as ts
# 设置token
ts.set_token('你的token')
pro = ts.pro_api()
# 获取平安银行2023年日线数据
df = pro.daily(
ts_code='000001.SZ',
start_date='20230101',
end_date='20231231',
fields='trade_date,open,high,low,close,vol'
)
# 排序,Tushare默认倒序
df = df.sort_values('trade_date').reset_index(drop=True)
print(df.head())
这里有个坑:Tushare的日线数据默认是后复权吗?不是。它返回的是原始价格。你需要自己调用 pro.adj_factor() 来获取复权因子,然后手动计算。我曾经因为忘了这一步,回测结果虚高了20%。
4.3 用AkShare获取数据
AkShare更直接,不用注册:
pip install akshare
获取同样的数据:
import akshare as ak
# 获取平安银行日线
df = ak.stock_zh_a_hist(
symbol="000001",
period="daily",
start_date="20230101",
end_date="20231231",
adjust="qfq" # 前复权
)
# 重命名列,统一格式
df.rename(columns={
'日期': 'trade_date',
'开盘': 'open',
'收盘': 'close',
'最高': 'high',
'最低': 'low',
'成交量': 'vol'
}, inplace=True)
print(df.head())
你看,AkShare直接支持复权参数,省事。但它的数据源是东方财富,偶尔会有断更。我遇到过某天下午4点数据还没更新,等到晚上才出来。所以做定时任务时,建议把获取时间设在收盘后2小时。
4.4 缺失值处理
数据拿到手,第一件事就是检查缺失。为什么会有缺失?停牌、数据源漏了、节假日不同步,原因很多。
先看看缺失情况:
# 检查缺失比例
missing_ratio = df.isnull().sum() / len(df)
print(missing_ratio[missing_ratio > 0])
处理方式,我按场景分三类:
- 直接删除:缺失比例小于5%,且是随机缺失。用
df.dropna()就行。 - 向前填充:金融时间序列常用。比如停牌日,用前一天的收盘价填充。用
df.fillna(method='ffill')。 - 插值法:如果缺失的是中间某段,比如日内分钟数据,用线性插值更合理。用
df.interpolate()。
举个例子,处理停牌:
# 假设某股票停牌5天,这5天数据全为NaN
# 用前一天的收盘价填充
df['close'] = df['close'].fillna(method='ffill')
# 成交量填0
df['vol'] = df['vol'].fillna(0)
4.5 异常值检测
异常值,说白了就是「一眼假」的数据。比如某股票突然一天涨了1000%,或者成交量突然比平时大了100倍。这通常是数据错误,不是市场行为。
我常用的检测方法:
- 3σ原则:假设数据服从正态分布,超出均值±3倍标准差的就是异常。适合收益率这类数据。
- 箱线图法:用四分位数,超出1.5倍IQR的算异常。适合价格、成交量。
- 固定阈值:比如A股涨跌幅限制±10%,超出这个范围的直接标记。
代码实现:
# 3σ法检测收益率异常
df['return'] = df['close'].pct_change()
mean = df['return'].mean()
std = df['return'].std()
df['return_outlier'] = (df['return'] > mean + 3*std) | (df['return'] < mean - 3*std)
# 查看异常
outliers = df[df['return_outlier']]
print(f"发现 {len(outliers)} 个异常值")
4.6 数据标准化
标准化,就是把不同量纲的数据拉到同一个尺度。比如股价是几十块,成交量是几百万手,直接放一起建模,模型会忽略成交量。
常用的两种方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score标准化 | (x - μ) / σ | 数据近似正态分布,如收益率 |
| Min-Max归一化 | (x - min) / (max - min) | 数据有明确边界,如RSI指标 |
代码实现:
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Z-score标准化
scaler = StandardScaler()
df['close_z'] = scaler.fit_transform(df[['close']])
# Min-Max归一化
scaler = MinMaxScaler()
df['close_mm'] = scaler.fit_transform(df[['close']])
这里有个细节:标准化参数要在训练集上拟合,然后应用到测试集。否则会造成未来信息泄露。我见过有人对整个数据集做标准化,然后做回测,结果过拟合得一塌糊涂。
4.7 完整流程:从获取到清洗
把上面所有步骤串起来,形成一个函数:
def get_clean_data(ts_code, start, end):
# 1. 获取数据
df = pro.daily(ts_code=ts_code, start_date=start, end_date=end)
df = df.sort_values('trade_date').reset_index(drop=True)
# 2. 缺失值处理
df['close'] = df['close'].fillna(method='ffill')
df['vol'] = df['vol'].fillna(0)
# 3. 异常值检测(涨跌幅限制)
df['return'] = df['close'].pct_change()
df = df[df['return'].between(-0.1, 0.1)] # 剔除异常涨跌
# 4. 标准化
scaler = StandardScaler()
df['close_std'] = scaler.fit_transform(df[['close']])
return df, scaler
这个流程,我用了好几年。每次新项目,直接复制粘贴,改改参数就行。你想想看,如果每次都要从头写一遍,多浪费时间。
4.8 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
你看,整个流程不是线性的。实际工作中,你经常需要来回调整。比如标准化后发现异常值更明显了,那就回去重新调异常检测的参数。这很正常,别怕麻烦。
好了,数据获取与清洗就讲到这里。下一章咱们聊聊因子构建,那才是真正出彩的地方。
公众号:蓝海资料掘金营,微信deep3321