第一章:金融数据获取与清洗——量化策略的“原材料”工程
做量化投资,尤其是公募指数增强,有个铁律:策略的上限,取决于数据的质量。我见过太多人花几个月调参数、换模型,结果回头一看,数据源里藏着几个坑,直接把夏普比率拉低了0.3。嗯,今天我们就从最基础也最关键的环节开始——把数据这摊子事彻底理清楚。
核心观点:数据清洗占量化策略开发工作量的60%以上。别急着写策略,先把数据伺候好。
1.1 股票行情数据获取:日线与分钟线
行情数据是策略的“心跳”。我个人习惯把数据源分成两类:免费公开源(如AKShare、Tushare Pro的免费额度)和付费商业源(如Wind、聚宽、恒生)。对于公募级别的实盘,付费源是必须的——免费源在分钟线精度和历史回溯上偶尔会“丢帧”,我踩过这个坑。
日线数据获取
日线是最基础的。你需要包含:open、high、low、close、volume、amount。注意,amount(成交额)比volume(成交量)更能反映资金真实意图,尤其在处理停牌复牌时。
import akshare as ak
import pandas as pd
# 获取沪深300成分股日线(示例:贵州茅台)
df_daily = ak.stock_zh_a_hist(symbol="600519",
period="daily",
start_date="20200101",
end_date="20231231",
adjust="qfq") # 前复权
print(df_daily.head())
避坑指南:我曾经直接用原始收盘价做回测,结果发现分红除权后净值曲线出现“断崖式下跌”。记住:回测必须用复权价,否则你的策略会“假赚真亏”。
分钟线数据获取
分钟线用于高频因子或日内择时。但注意,分钟线数据量巨大——沪深300全量股票1分钟线一年约2GB。我建议只保留5分钟或15分钟线做因子计算,除非你做T+0策略。
# 获取5分钟线(示例:使用akshare的分钟接口)
df_min = ak.stock_zh_a_hist_min_em(symbol="600519",
period="5",
start_date="2023-01-01",
end_date="2023-12-31")
df_min.columns = ['时间', '开盘', '收盘', '最高', '最低', '成交量', '成交额']
print(df_min.head())
1.2 财务数据获取:利润表与资产负债表
财务数据是基本面因子的“血液”。公募指数增强策略里,盈利质量因子(如ROE、毛利率)和成长因子(如营收增速)都依赖它。获取时要注意:财报发布日期和财报覆盖期是两回事。比如2023年4月发布的2022年年报,你只能在2023年4月之后才能用这个数据做因子。
利润表关键字段
| 字段名 | 含义 | 常见因子 |
|---|---|---|
| 营业收入 | 主营业务收入 | 营收增速 |
| 营业利润 | 核心业务利润 | 营业利润率 |
| 净利润 | 归母净利润 | ROE、净利率 |
| 扣非净利润 | 剔除一次性收益 | 扣非ROE |
# 获取利润表(示例:使用Tushare)
import tushare as ts
ts.set_token('你的token')
pro = ts.pro_api()
df_income = pro.income(ts_code='600519.SH',
start_date='20200101',
end_date='20231231',
fields='ts_code,end_date,revenue,profit,n_income')
print(df_income.head())
资产负债表关键字段
| 字段名 | 含义 | 常见因子 |
|---|---|---|
| 总资产 | 资产总计 | 资产周转率 |
| 总负债 | 负债合计 | 资产负债率 |
| 股东权益 | 归母权益 | BV、PB |
| 货币资金 | 现金及等价物 | 现金比率 |
注意:财务数据存在“滞后性”。比如一季报截止日是4月30日,但很多公司拖到最后一周才发布。你如果提前用“预估数据”做因子,回测会严重过拟合。我建议统一使用实际发布日期对齐。
1.3 数据清洗与对齐:复权处理与缺失值处理
数据清洗是量化工程师的“基本功”。说白了,就是把原始数据里的“脏东西”筛掉,让策略跑在干净的数据上。
复权处理
复权分为前复权和后复权。我个人习惯用前复权做回测——它调整了历史价格,使得当前价格是真实的。但注意:前复权会导致历史价格出现负值(比如茅台早期分红多),这时候需要做截断处理。
# 手动前复权示例
def adjust_forward(df):
# 假设df包含close和adjust_factor(复权因子)
df['close_adj'] = df['close'] * df['adjust_factor']
df['open_adj'] = df['open'] * df['adjust_factor']
# 其他字段同理
return df
我的经验:对于指数增强策略,我建议同时保留复权价和原始价。复权价用于计算收益率,原始价用于计算估值指标(如PE、PB)。因为估值是用原始价格算的,用复权价算PE会出错。
缺失值处理
缺失值在金融数据里很常见:停牌、新股上市、财报未发布等。处理方法有三种:
- 向前填充(ffill):适用于停牌数据,用最近一个交易日的数据填充。这是最常用的方法。
- 向后填充(bfill):适用于新股上市,用第一个交易日的数据填充。
- 删除:如果缺失比例超过20%,直接删除该股票或该时间段。
# 缺失值处理示例
df.fillna(method='ffill', inplace=True) # 向前填充
df.dropna(thresh=len(df.columns)*0.8, inplace=True) # 删除缺失过多的行
1.4 数据存储:HDF5与Parquet
数据存储是量化系统的“仓库”。你想想看,沪深300全量股票5年日线数据大约500MB,分钟线数据可能超过50GB。用CSV存?加载一次要几分钟,而且容易内存溢出。我推荐两种格式:HDF5和Parquet。
HDF5:适合分层存储
HDF5支持按股票代码分组存储,读取时只加载需要的股票,非常高效。我习惯把日线、分钟线、财务数据分别存到不同的group里。
import h5py
# 写入HDF5
with h5py.File('stock_data.h5', 'w') as f:
f.create_dataset('600519/daily', data=df_daily.values)
f.create_dataset('600519/min5', data=df_min.values)
# 读取HDF5
with h5py.File('stock_data.h5', 'r') as f:
data = f['600519/daily'][:]
Parquet:适合列式存储
Parquet是列式存储格式,压缩率高,读取速度快。尤其适合存储多股票、多字段的宽表数据。我建议用fastparquet或pyarrow引擎。
import pandas as pd
# 写入Parquet
df.to_parquet('stock_data.parquet', engine='pyarrow', compression='snappy')
# 读取Parquet
df = pd.read_parquet('stock_data.parquet', engine='pyarrow')
我的建议:对于公募级别的量化系统,我推荐HDF5存储原始数据(方便按股票切片),Parquet存储清洗后的因子数据(方便按时间切片)。两者结合,效率最高。
本章知识体系总览
这张图把本章的知识结构串起来了。你可以看到,数据获取是源头,清洗是核心,存储是终点。任何一个环节出问题,策略都会“带病运行”。
最后说一句:数据工程没有捷径。我见过太多人想跳过清洗直接建模,结果回测曲线漂亮得像假的一样——嗯,它确实是假的。把数据搞干净,你的策略就成功了一半。