第二章 数据获取与清洗:股票行情数据获取(日线、分钟线)、财务数据获取、数据清洗与预处理(去极值、填充缺失值)
做量化,数据就是你的弹药库。弹药不行,枪法再好也白搭。
这一章,咱们聊聊怎么搞到靠谱的数据,以及怎么把这些原始数据收拾得服服帖帖。我个人习惯把数据获取和清洗看作整个策略的生命线——源头脏了,后面所有模型都是垃圾进垃圾出。
2.1 股票行情数据获取(日线、分钟线)
行情数据,说白了就是股票每天或每分钟的价格、成交量这些。日线最常见,分钟线则用于高频或日内策略。
2.1.1 日线数据获取
日线数据,我一般从几个渠道拿:
- 免费API:比如 Tushare、AKShare、Baostock。适合小规模研究。
- 商业数据源:Wind、Choice、聚宽。数据质量高,但贵。
- 自建数据库:用 MySQL 或 InfluxDB 存历史数据,每天增量更新。
举个例子,用 AKShare 拿日线,代码很简单:
import akshare as ak
# 获取平安银行日线数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily",
start_date="20230101", end_date="20231231")
print(df.head())
嗯,这里要注意:免费接口经常有频率限制。我遇到过一天拉5000只股票,结果被 ban 了IP。后来我加了随机延时,才稳定下来。
2.1.2 分钟线数据获取
分钟线数据量巨大。拿1分钟线来说,一只股票一天就有240条记录。全市场5000只股票,一天就是120万条。
我个人习惯用 多线程+分批下载。比如:
import akshare as ak
import time
from concurrent.futures import ThreadPoolExecutor
def fetch_minute(symbol):
try:
df = ak.stock_zh_a_hist_minute(symbol=symbol, period="1")
time.sleep(0.5) # 防止被封
return df
except:
return None
# 用线程池并行拉取
symbols = ["000001", "000002", "000003"]
with ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(fetch_minute, symbols))
你想想看,如果单线程一个一个拉,5000只股票得拉到猴年马月。多线程能快10倍以上。
2.2 财务数据获取
财务数据是基本面因子的来源。比如市盈率、市净率、ROE这些。
财务数据获取比行情数据麻烦。因为财务数据有 报告期 和 公告日 的区别。报告期是数据对应的会计期间,公告日是实际发布的日子。做因子时,必须用公告日的数据,否则就用了未来信息。
2.2.1 常用财务数据字段
| 字段 | 含义 | 频率 |
|---|---|---|
| 营业收入 | 公司主营业务收入 | 季度 |
| 净利润 | 扣非后净利润 | 季度 |
| 每股净资产 | 净资产/总股本 | 季度 |
| ROE | 净利润/净资产 | 季度 |
获取财务数据,我常用 Tushare 的 financial 接口:
import tushare as ts
pro = ts.pro_api('你的token')
df = pro.fina_indicator(ts_code='000001.SZ',
start_date='20230101',
end_date='20231231')
print(df[['end_date', 'roe', 'eps']])
这里有个坑:财务数据经常有 修正。比如公司发了一季报,后来又发更正公告。如果你用旧数据,因子值就是错的。我建议每次拉取时,都拉最新的版本。
2.3 数据清洗与预处理
数据拿到手,千万别直接用。原始数据里全是坑:缺失值、极端值、重复数据……
我总结了一套清洗流程,叫 「三去一补」:去重复、去极值、去异常、补缺失。
2.3.1 去极值(Winsorization)
极值就是那些离谱的数据。比如某股票一天涨了100倍,这明显是数据错误。或者某只小盘股突然放量100倍,也可能是异常。
我个人习惯用 MAD(中位数绝对偏差) 方法去极值。比3倍标准差更稳健:
import numpy as np
def winsorize_mad(series, n=5):
"""用MAD方法去极值"""
median = series.median()
mad = np.median(np.abs(series - median))
upper = median + n * mad
lower = median - n * mad
return series.clip(lower, upper)
# 示例:对收益率去极值
returns = df['pct_chg']
df['pct_chg_clean'] = winsorize_mad(returns)
为什么用MAD?因为标准差对极值本身就很敏感。你想想看,如果数据里有一个100倍的标准差,那算出来的标准差本身就很大,再去极值就没效果了。MAD用中位数,不受极值影响。
2.3.2 填充缺失值
缺失值在金融数据里太常见了。停牌、新股上市、数据缺失……
填充方法,我按场景分:
- 行情数据:用前向填充(ffill)。比如停牌期间,价格不变。
- 财务数据:用最近一期数据填充。比如季报缺失,用上一季度的。
- 因子数据:用截面中位数填充。避免引入未来信息。
代码示例:
# 前向填充行情数据
df['close'] = df['close'].ffill()
# 截面中位数填充因子
factor_df = factor_df.fillna(factor_df.median())
2.3.3 去重复与对齐
重复数据也是常见问题。比如同一个交易日,数据被拉了两遍。
我一般用 drop_duplicates 处理:
df = df.drop_duplicates(subset=['ts_code', 'trade_date'])
然后做 时间对齐。不同数据源的时间戳可能不一样。比如日线数据,有的用 '2023-01-01',有的用 '20230101'。统一转成 datetime 格式:
df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')
2.4 本章知识体系
下面这张图,是我自己总结的数据获取与清洗全流程。你可以把它贴在工位上:
核心要点:数据清洗不是一次性工作。每次新增数据,都要跑一遍清洗流程。我习惯把清洗写成函数,每天定时跑。
好了,这一章的内容就这些。数据获取和清洗,说白了就是「把脏活干漂亮」。你想想看,如果连数据都搞不定,后面那些花里胡哨的因子模型,全是空中楼阁。
我个人经验是:花80%的时间在数据上,20%的时间建模。数据干净了,模型自然就work了。
公众号:蓝海数据掘金营,微信deep3321