第三章:金融数据获取与清洗——多源数据接入实战
做量化投研,数据就是你的弹药库。弹药不行,再好的策略也白搭。
这一章,咱们聊聊怎么把Tushare、AKShare、Wind这些数据源接入进来,再把这些脏数据洗干净。我个人习惯把数据获取和清洗看作一个整体流程——你想想看,数据拿进来就指望它干净?不存在的。
3.1 多源数据接入:三驾马车怎么选?
国内量化圈,数据源基本就这三家:Tushare、AKShare、Wind。我自己的经验是,不同场景用不同工具。
| 数据源 | 特点 | 适用场景 | 费用 |
|---|---|---|---|
| Tushare | 结构化好,文档清晰,社区活跃 | 个人研究、回测、因子挖掘 | 免费+积分制 |
| AKShare | 开源免费,覆盖广,更新快 | 快速原型、小规模策略 | 完全免费 |
| Wind | 机构级,数据质量高,接口稳定 | 实盘交易、机构投研 | 付费(较贵) |
嗯,这里要注意:千万别在实盘环境用免费数据源做高频交易。我在项目中遇到过,AKShare某天突然接口变了,导致策略直接断粮。血的教训。
3.1.1 Tushare接入实战
Tushare需要先注册获取token。我个人习惯把token放在环境变量里,而不是硬编码在代码中。
import tushare as ts
import os
# 设置token(建议从环境变量读取)
ts.set_token(os.getenv('TUSHARE_TOKEN', '你的token'))
pro = ts.pro_api()
# 获取日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20240101', end_date='20240301')
print(df.head())
为什么推荐环境变量?因为代码要上Git,token泄露了你就等着被薅羊毛吧。我曾经见过有人把token直接commit到公开仓库,第二天积分就被刷光了。
3.1.2 AKShare接入实战
AKShare更轻量,不需要token,直接pip install akshare就能用。
import akshare as ak
# 获取A股日线数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily",
start_date="20240101", end_date="20240301", adjust="qfq")
print(df.head())
AKShare的优点是快,缺点是接口名称经常变。我建议每次升级前先看看更新日志,不然代码跑着跑着就报错了。
3.1.3 Wind接入实战
Wind是机构标配,但需要安装Wind客户端并启动。接口是w.start()和w.wsd()。
from WindPy import w
w.start() # 启动Wind接口
# 获取日线数据
df = w.wsd("000001.SZ", "open,high,low,close,volume",
"2024-01-01", "2024-03-01", "")
print(df.Data)
Wind的坑在于:如果客户端没启动,w.start()会卡住。我建议加个超时处理,或者先检查进程是否存在。
3.2 数据清洗实战:脏数据怎么治?
数据拿进来,第一件事不是分析,是清洗。你想想看,金融数据里什么妖魔鬼怪都有。
3.2.1 缺失值处理
缺失值在金融数据里太常见了。停牌、节假日、数据源漏了,都会导致NaN。
import pandas as pd
import numpy as np
# 模拟缺失数据
df = pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=10, freq='D'),
'close': [100, 101, np.nan, 103, 104, np.nan, 106, 107, 108, 109]
})
# 方法1:前向填充(推荐用于日频数据)
df['close_ffill'] = df['close'].ffill()
# 方法2:线性插值(适用于短时间缺失)
df['close_interp'] = df['close'].interpolate(method='linear')
# 方法3:直接删除(缺失太多就别要了)
df_drop = df.dropna(subset=['close'])
我个人习惯:日频数据用前向填充,因为停牌后复牌,价格一般会补涨补跌。但如果是分钟级数据,我建议用插值,因为前向填充会引入滞后偏差。
3.2.2 异常值处理
异常值比缺失值更隐蔽。涨停跌停、数据录入错误、除权除息,都会产生异常值。
# 用Z-score检测异常值
from scipy import stats
df['z_score'] = np.abs(stats.zscore(df['close'].dropna()))
df['is_outlier'] = df['z_score'] > 3
# 用IQR方法(更稳健)
Q1 = df['close'].quantile(0.25)
Q3 = df['close'].quantile(0.75)
IQR = Q3 - Q1
df['is_outlier_iqr'] = (df['close'] < Q1 - 1.5*IQR) | (df['close'] > Q3 + 1.5*IQR)
嗯,这里要注意:金融数据的异常值不一定是错误。比如某天突然暴涨,可能是利好公告。我建议先标记异常值,再人工判断,而不是直接删除。
3.2.3 复权处理
复权是量化投研里最容易被忽视的坑。不复权的话,分红送股会导致价格跳空,回测结果完全失真。
| 复权类型 | 说明 | 适用场景 |
|---|---|---|
| 前复权 | 调整历史价格,保持最新价格不变 | 回测、因子计算 |
| 后复权 | 调整当前价格,保持历史价格不变 | 长期趋势分析 |
| 不复权 | 原始价格,有跳空 | 实盘交易(按实际价格成交) |
# Tushare获取前复权数据
df = pro.daily(ts_code='000001.SZ', start_date='20240101',
end_date='20240301', adj='qfq')
# AKShare获取前复权数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily",
start_date="20240101", end_date="20240301",
adjust="qfq")
3.3 数据存储:Parquet vs Feather
数据洗干净了,存哪里?CSV?别闹了,几百万条数据CSV读写慢得要命。
我个人推荐Parquet和Feather,两者都是列式存储,压缩率高,读写快。
| 格式 | 压缩率 | 读写速度 | 跨语言支持 | 适用场景 |
|---|---|---|---|---|
| Parquet | 高(可压缩到10%-20%) | 快 | 好(Spark、Hive等) | 大数据、长期存储 |
| Feather | 中(约50%) | 极快 | 一般(R/Python为主) | 中间结果、快速读写 |
import pandas as pd
# 保存为Parquet
df.to_parquet('stock_data.parquet', compression='snappy')
# 保存为Feather
df.to_feather('stock_data.feather')
# 读取
df_parquet = pd.read_parquet('stock_data.parquet')
df_feather = pd.read_feather('stock_data.feather')
我自己的习惯:原始数据存Parquet,因为压缩率高,省硬盘。中间处理结果存Feather,因为读写快,迭代开发时省时间。
3.4 本章知识体系
下面这张图,是我自己梳理的数据获取与清洗流程。你照着这个框架走,基本不会漏掉关键环节。
说白了,数据获取与清洗就是量化投研的「地基工程」。地基没打好,上面盖多高的楼都得塌。我见过太多人花90%时间调策略,却不愿意花10%时间把数据搞干净。结果呢?回测曲线漂亮得像假的一样,实盘一跑就露馅。
嗯,这一章的内容就到这儿。记住:数据干净了,策略才有意义。