第四章:数据获取与清洗——金融Agent的“食材准备”

做量化回测,数据就是你的命根子。我见过太多人,策略写得天花乱坠,结果数据一塌糊涂,回测结果根本没法看。说白了,数据获取与清洗,就是给金融Agent准备食材——食材不新鲜,厨艺再好也白搭。

这一章,咱们就聊聊怎么用yfinance拿数据,怎么把脏数据洗干净,怎么对齐不同频率的数据。嗯,都是我在实战中踩过的坑,今天一并倒给你。

4.1 用yfinance获取股票数据

yfinance这个库,我用了好几年了。它免费、简单,而且数据质量对于回测来说基本够用。我个人习惯是把它当作数据源的第一道关口。

先装库:

pip install yfinance pandas numpy

然后看个例子:

import yfinance as yf
import pandas as pd

# 获取贵州茅台的历史数据
ticker = "600519.SS"
data = yf.download(ticker, start="2020-01-01", end="2023-12-31")
print(data.head())

这里要注意,A股代码后面要加后缀。上海交易所是.SS,深圳交易所是.SZ。我曾经因为忘了加后缀,查了半天数据都是空的,后来才发现是代码写错了。

小技巧: yfinance默认返回的数据包含Open、High、Low、Close、Volume、Adj Close。对于回测,我建议用调整后的收盘价(Adj Close),因为它考虑了分红、拆股等因素。

如果你需要多只股票,可以这样:

tickers = ["600519.SS", "000001.SZ", "601318.SS"]
data = yf.download(tickers, start="2020-01-01", end="2023-12-31")
# 这会返回一个MultiIndex的DataFrame
close_prices = data['Adj Close']
print(close_prices.head())

你想想看,一次拿几十只股票的数据,几秒钟就搞定了。这在以前,得手动从各种网站爬,效率天差地别。

4.2 数据清洗——缺失值与异常值

数据拿到手,别急着用。先看看有没有坑。我遇到过最离谱的一次,某只股票连续三天收盘价都是同一个数字,一看就是数据源出了问题。

4.2.1 缺失值处理

金融数据里,缺失值很常见。比如节假日、停牌、数据源中断等。我们先检查一下:

# 检查缺失值
print(data.isnull().sum())

# 查看缺失值比例
missing_ratio = data.isnull().mean()
print(missing_ratio[missing_ratio > 0])

处理缺失值,我一般用这几种方法:

方法 适用场景 代码示例
向前填充 停牌、节假日(用上一个交易日数据) data.fillna(method='ffill')
向后填充 数据延迟(用下一个交易日数据) data.fillna(method='bfill')
插值法 少量缺失,且趋势明显 data.interpolate()
直接删除 缺失比例过高(比如超过20%) data.dropna()

我个人习惯是:对于日线数据,先用向前填充。因为停牌后复牌,价格通常不会跳空太多。但如果是分钟级数据,我会用插值法,因为高频数据对连续性要求更高。

注意: 千万不要在回测中“偷看未来”。比如用未来的数据填充过去的缺失值,那回测结果就完全失真了。我曾经见过有人用后一天的收盘价填充前一天的缺失,结果回测收益翻倍,实盘亏成狗。

4.2.2 异常值处理

异常值,说白了就是那些明显不合理的数据。比如某只股票突然涨了1000%,或者成交量突然变成0。这些通常都是数据录入错误。

我常用的检测方法:

# 计算日收益率
returns = data['Adj Close'].pct_change()

# 找出收益率绝对值超过20%的日期
outliers = returns[abs(returns) > 0.2]
print(outliers)

对于A股,单日涨跌幅限制是10%(ST股是5%)。如果出现超过这个范围的,基本可以判定是异常值。处理方式:

  • 替换为NaN,然后用缺失值处理方法填充
  • 用前后两天的均值替换
  • 直接删除该行数据(如果异常值不多)
# 将异常值替换为NaN
data.loc[abs(returns) > 0.2, 'Adj Close'] = None
# 然后用向前填充
data['Adj Close'] = data['Adj Close'].fillna(method='ffill')

4.3 数据对齐与重采样

做多资产回测时,不同股票的交易日期可能不一样。比如A股和港股,节假日不同,数据长度就不一致。这时候就需要对齐。

4.3.1 数据对齐

对齐的核心思想是:只保留所有股票都有数据的日期。

# 假设我们有两只股票的数据
df1 = yf.download("600519.SS", start="2020-01-01", end="2023-12-31")['Adj Close']
df2 = yf.download("000001.SZ", start="2020-01-01", end="2023-12-31")['Adj Close']

# 合并并自动对齐
combined = pd.concat([df1, df2], axis=1, keys=['茅台', '平安'])
# 删除任何一行有缺失的数据
combined_aligned = combined.dropna()
print(combined_aligned.head())

这里有个坑:如果某只股票停牌时间很长,dropna会删掉大量数据。我建议先看看缺失比例,如果超过10%,可以考虑用填充而不是删除。

4.3.2 重采样

有时候我们需要把日线数据转换成周线或月线。重采样就是干这个的。

# 将日线重采样为周线
weekly_data = data['Adj Close'].resample('W').last()
# 将日线重采样为月线
monthly_data = data['Adj Close'].resample('M').last()

# 计算周收益率
weekly_returns = weekly_data.pct_change()

重采样的频率参数:

参数 含义
W 周(默认周日为结束)
M 月(月末)
Q 季度
Y
B 工作日

你想想看,做长周期策略时,用日线数据计算信号,计算量巨大。重采样成周线或月线,效率能提升好几倍。

核心要点: 数据清洗不是一次性工作。每次拿到新数据,都要重新检查一遍。我习惯写一个数据预处理函数,把缺失值、异常值、对齐、重采样都封装进去,每次调用就行。

4.4 本章知识体系

下面这张图,是我自己总结的数据处理流程。你照着这个顺序做,基本不会出大问题。

金融Agent数据获取与清洗流程 1. 数据获取 yfinance下载 2. 缺失值处理 ffill/bfill/插值 3. 异常值检测 涨跌幅阈值判断 4. 数据对齐 多资产日期对齐 5. 重采样 日线→周线/月线 6. 输出清洗数据 用于回测引擎 注意:每一步都可能需要回溯 比如发现异常值后,可能需要重新检查数据源 清洗不是线性的,而是一个迭代过程

嗯,这张图把整个流程串起来了。你照着这个顺序做,基本不会漏掉关键步骤。我个人习惯是把每一步都写成函数,然后串成一个pipeline,这样每次跑新数据,一行代码就搞定了。

避坑指南: 我曾经因为数据对齐没做好,导致回测里用了未来数据。具体来说,A股和港股节假日不同,我直接用concat合并,结果港股的数据比A股多几天,回测时用港股的数据预测A股,收益高得离谱。后来发现这个问题,改完对齐逻辑,收益直接腰斩。嗯,这才是真实水平。

数据清洗这件事,说白了就是“垃圾进,垃圾出”。你花80%的时间在数据上,剩下20%的时间写策略,这才是正确的比例。别想着一步到位,数据这东西,越洗越干净。


专注资料整理