数据基石:金融数据获取与清洗
做量化投资,说白了就是跟数据打交道。我见过太多人把精力全花在策略模型上,结果数据源一换,模型直接崩了。嗯,这章我们就来啃这块硬骨头——怎么拿到干净、对齐的金融数据。
1. 数据源的选择:Tushare vs AkShare
国内做量化,绕不开两个库:Tushare 和 AkShare。我个人习惯是两者搭配着用。
- Tushare:老牌金融数据接口,数据质量高,但部分接口需要积分。适合做日线、基本面数据。
- AkShare:开源免费,覆盖广,连期货、期权、宏观数据都有。适合做高频、另类数据。
我在项目中遇到过一个问题:用 Tushare 拿到的复权因子,跟 AkShare 的复权方式不一样。结果回测曲线差了好几个点。所以,数据源一致性是第一原则。
核心原则:同一个因子,尽量用同一个数据源。混用前,务必做交叉验证。
2. 实战:用 Tushare 获取股票日线数据
先装库:
pip install tushare akshare pandas numpy
然后写个获取函数。我习惯把 token 放在环境变量里,别硬编码在代码中。
import tushare as ts
import pandas as pd
# 初始化
pro = ts.pro_api('你的token')
# 获取平安银行日线
df = pro.daily(
ts_code='000001.SZ',
start_date='20230101',
end_date='20231231'
)
# 按日期排序
df = df.sort_values('trade_date').reset_index(drop=True)
print(df.head())
这里有个坑:Tushare 返回的日期是字符串,不是 datetime 类型。我建议你拿到数据后,第一时间转换:
df['trade_date'] = pd.to_datetime(df['trade_date'])
注意:Tushare 的复权数据有前复权和后复权之分。做因子回测,我一般用后复权。为什么?后复权保留了历史真实价格,计算收益率更准确。
3. 数据对齐:多标的、多频率的噩梦
你想想看,你有 3000 只股票,每只股票的交易日期还不一样。有的停牌了,有的退市了。怎么对齐?
我常用的方法是 构建一个统一的时间索引。
# 假设我们有两只股票的数据
stock_a = pd.DataFrame({
'date': ['2023-01-01', '2023-01-03', '2023-01-04'],
'close': [10, 11, 12]
})
stock_b = pd.DataFrame({
'date': ['2023-01-01', '2023-01-02', '2023-01-04'],
'close': [20, 21, 22]
})
# 构建统一日期索引
all_dates = pd.date_range('2023-01-01', '2023-01-05', freq='D')
# 对齐
a_aligned = stock_a.set_index('date').reindex(all_dates)
b_aligned = stock_b.set_index('date').reindex(all_dates)
print(a_aligned)
print(b_aligned)
你看,缺失的日期变成了 NaN。这就是我们要处理的缺失值。
4. 缺失值处理:不是简单的填充
很多人一上来就用均值填充。嗯,这里要注意:金融数据的缺失,往往是有原因的。
- 停牌:用前一天的收盘价填充,或者直接剔除。
- 非交易日:用前向填充(ffill),因为价格不变。
- 数据源缺失:用行业均值,或者用线性插值。
我曾经犯过一个错:用 0 填充缺失的收益率。结果回测曲线漂亮得不行,实盘直接亏成狗。为什么?因为 0 填充相当于假设那天没涨没跌,但实际可能是停牌了,复牌后直接补跌。
我的经验:对于日线数据,优先用 ffill(前向填充)。对于分钟数据,用 interpolate(线性插值)更合理。
# 前向填充
df_filled = df.ffill()
# 线性插值
df_interpolated = df.interpolate(method='linear')
5. 知识体系:一张图看懂数据流程
下面这张 SVG 图,是我自己总结的数据处理流程。你照着这个顺序做,基本不会出大问题。
6. 实战:完整的数据清洗流水线
最后,我给你一个可以直接用的函数。这是我做项目时积累下来的模板。
def clean_stock_data(df, date_col='trade_date', price_col='close'):
"""
股票数据清洗流水线
"""
# 1. 日期处理
df[date_col] = pd.to_datetime(df[date_col])
df = df.sort_values(date_col).reset_index(drop=True)
# 2. 去除全空行
df = df.dropna(how='all')
# 3. 价格异常值处理(涨跌幅超过20%视为异常)
df['pct_chg'] = df[price_col].pct_change()
abnormal = df['pct_chg'].abs() > 0.2
df.loc[abnormal, price_col] = None
# 4. 缺失值填充
df[price_col] = df[price_col].ffill()
# 5. 删除开头可能存在的NaN
df = df.dropna(subset=[price_col])
return df
# 使用示例
clean_df = clean_stock_data(df)
print(f"清洗前:{len(df)} 行,清洗后:{len(clean_df)} 行")
小技巧:清洗完数据后,我习惯画个分布图看看。如果价格序列出现阶梯状,说明填充方式有问题。
数据清洗这件事,说白了就是「垃圾进,垃圾出」。你花 80% 的时间处理数据,剩下 20% 的时间建模,这才是量化投资的常态。别嫌麻烦,这一步做扎实了,后面的因子挖掘才能站得住脚。