4、数据获取与清洗:数据来源(Wind、Tushare、Yahoo Finance)、数据清洗流程、处理缺失值与异常值、数据对齐与重采样

做量化的人都知道一句话:垃圾进,垃圾出。模型再牛,数据不行,一切都是白搭。今天我们就来聊聊数据获取与清洗这档子事。我个人习惯把这一步叫做「做饭前的洗菜切菜」——虽然不显眼,但决定了最终菜品的生死。

4.1 数据来源:三驾马车

目前国内做多因子研究,主流的数据源就三个:Wind、Tushare、Yahoo Finance。我挨个说说它们的脾气。

4.1.1 Wind(万得)

Wind 是机构标配。数据全、质量高、更新快。但价格不菲,个人用户基本用不起。我在私募那会儿,公司每年交几十万的 Wind 终端费,就为了拿那几根 K 线。如果你在机构工作,直接用 Wind API 就好。代码示例:

from WindPy import w
w.start()
# 获取平安银行日行情
data = w.wsd("000001.SZ", "open,high,low,close,volume", "2024-01-01", "2024-12-31")
print(data)

4.1.2 Tushare(通联数据)

Tushare 是个人玩家的福音。免费版够用,Pro 版也不贵。数据覆盖 A 股、基金、期货、宏观等。我建议新手从 Tushare 入手。不过要注意,Tushare 的积分制度有点绕,有些数据需要 2000 积分以上才能调取。代码示例:

import tushare as ts
ts.set_token('你的token')
pro = ts.pro_api()
# 获取日线行情
df = pro.daily(ts_code='000001.SZ', start_date='20240101', end_date='20241231')
print(df.head())

4.1.3 Yahoo Finance

做美股或者全球资产配置,Yahoo Finance 是首选。免费、接口简单。但有个坑——Yahoo 的 API 经常被墙,国内访问不稳定。我曾经半夜爬起来跑数据,结果发现 Yahoo 又连不上了,气得我直接换了数据源。代码示例:

import yfinance as yf
# 获取苹果公司数据
aapl = yf.download('AAPL', start='2024-01-01', end='2024-12-31')
print(aapl.head())
我的建议: 如果你做 A 股,优先用 Tushare;做美股用 Yahoo;机构用户用 Wind。三个数据源可以交叉验证,防止单数据源出错。

4.2 数据清洗流程:从脏数据到干净数据

数据拿到手,第一件事不是建模,而是清洗。我见过太多人直接拿原始数据跑模型,结果回测曲线漂亮得像假的一样——其实就是假的,因为数据里有未来函数。

清洗流程我总结为五步:

  1. 格式统一:日期格式、列名、数据类型全部标准化
  2. 缺失值处理:该填充的填充,该删除的删除
  3. 异常值检测:剔除明显错误的数据点
  4. 数据对齐:不同频率的数据对齐到同一时间轴
  5. 重采样:按需调整数据频率

下面我们一个个展开说。

4.3 处理缺失值与异常值

4.3.1 缺失值处理

缺失值在金融数据里太常见了。停牌、节假日、数据源漏传,都会导致缺失。处理方式有三种:

  • 删除法:直接删掉有缺失的行。简单粗暴,但会损失信息。
  • 填充法:用前值、后值、均值、中位数填充。我个人习惯用前值填充(ffill),因为金融数据有连续性。
  • 插值法:线性插值、多项式插值等。适合少量缺失。

代码示例:

import pandas as pd
import numpy as np

# 模拟缺失数据
df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=10, freq='D'),
    'close': [100, 102, np.nan, 105, 107, np.nan, 110, 112, 115, np.nan]
})

# 前值填充
df['close_ffill'] = df['close'].ffill()

# 线性插值
df['close_interp'] = df['close'].interpolate(method='linear')

print(df)
注意: 千万不要用未来数据填充!比如用后一天的收盘价填充前一天的缺失值,这就是典型的未来函数。我曾经在回测中犯过这个错,回测年化收益 50%,实盘直接亏到姥姥家。

4.3.2 异常值处理

异常值通常来自数据录入错误、极端行情或者除权除息未调整。检测方法有:

  • 3σ 原则:超出均值±3倍标准差的数据视为异常
  • IQR 方法:超出 Q1-1.5*IQR 或 Q3+1.5*IQR 的数据视为异常
  • 百分比截尾:直接剔除上下 1% 或 5% 的数据

代码示例:

# 3σ 方法检测异常值
mean = df['close'].mean()
std = df['close'].std()
df['is_outlier_3sigma'] = (np.abs(df['close'] - mean) > 3 * std)

# 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)

print(df[df['is_outlier_3sigma'] | df['is_outlier_iqr']])
避坑指南: 我曾经在清洗沪深300成分股数据时,发现某只股票某天涨幅 1000%。一开始以为是异常值,后来一查是除权除息日没做复权处理。所以,先检查数据逻辑,再判定异常

4.4 数据对齐与重采样

4.4.1 数据对齐

多因子模型需要多个数据源的数据对齐到同一时间轴。比如,你同时用日频的行情数据和周频的财务数据,必须对齐到日频或者周频。对齐方法:

  • 左连接:以某个时间序列为主表,其他数据左连接上去
  • 重索引:统一索引后,用 reindex 方法对齐

代码示例:

# 假设有两个数据框
price_df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=5, freq='D'),
    'close': [100, 102, 104, 103, 105]
})

factor_df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=3, freq='2D'),
    'pe': [15, 16, 15.5]
})

# 以 price_df 为主表,左连接 factor_df
aligned_df = price_df.merge(factor_df, on='date', how='left')
print(aligned_df)

4.4.2 重采样

重采样就是把数据从一种频率转换到另一种频率。比如把日频数据转成周频、月频。常用方法:

  • 降采样:高频转低频,用 mean、sum、ohlc 等聚合函数
  • 升采样:低频转高频,用 ffill、bfill、interpolate 填充

代码示例:

# 日频转周频
df.set_index('date', inplace=True)
weekly_df = df['close'].resample('W').last()  # 取每周最后一个交易日收盘价
print(weekly_df)

# 日频转月频
monthly_df = df['close'].resample('M').mean()  # 取每月均值
print(monthly_df)
注意: 重采样时一定要考虑交易日的特殊性。A 股只有周一到周五交易,直接用 calendar 重采样会包含周末。我建议用 resample('W-FRI') 来指定周五为周的最后一天。

4.5 本章知识体系

下面我用一张 SVG 图来总结本章的核心逻辑。这张图展示了从数据获取到清洗完成的完整流程,你一看就明白。

数据获取与清洗核心流程 数据源 数据源 数据源 Wind Tushare Yahoo Finance 数据清洗流程 缺失值处理 异常值检测 数据对齐 重采样 干净可用的因子数据 数据清洗是量化策略的基石,花 80% 的时间在数据上,模型自然跑得好

嗯,这张图把整个流程串起来了。从三个数据源出发,经过清洗流程的五步,最终得到干净可用的因子数据。你想想看,如果这一步没做好,后面模型再花哨也是白搭。

核心要点:
  • 数据源选择:A 股用 Tushare,美股用 Yahoo,机构用 Wind
  • 缺失值处理:优先前值填充,避免使用未来数据
  • 异常值检测:3σ 和 IQR 方法结合使用,先检查数据逻辑
  • 数据对齐:以主时间序列为基准,左连接或重索引
  • 重采样:注意交易日历,避免包含非交易日

好了,数据获取与清洗就讲到这里。记住一句话:数据干净了,模型就成功了一半。下一章我们开始讲因子构建,那才是真正有意思的部分。

专注资料整理