2. 数据获取与清洗:金融数据源介绍与预处理
金融因子工程的第一步是获取可靠、对齐的原始数据。不同数据源在覆盖范围、更新频率、数据质量和获取成本上差异显著;而数据清洗(对齐、去重、缺失值处理、异常值检测)直接决定后续因子计算的有效性。本章系统梳理主流金融数据源,并给出标准化的清洗流程与代码实现。
2.1 主流金融数据源对比
量化研究常用的数据源可分为三类:商业终端、开源社区平台、以及交易所直连或第三方数据商。下表从数据覆盖、更新频率、成本与适用场景四个维度进行对比。
| 数据源 | 覆盖范围 | 更新频率 | 成本 | 典型适用场景 |
|---|---|---|---|---|
| Wind(万得) | A股、港股、美股、期货、债券、基金、宏观 | 实时 / 日频 / 分钟频 | 高(机构年费) | 机构投研、高频因子、实时风控 |
| Tushare Pro | A股、基金、期货、指数、宏观 | 日频 / 分钟频(积分制) | 中(积分付费) | 个人量化、回测研究、因子实验室 |
| Yahoo Finance | 全球股票、ETF、指数、外汇、加密货币 | 日频 / 实时(延迟) | 免费 | 全球多资产回测、学术研究、原型验证 |
| Quandl / Nasdaq Data Link | 美股、另类数据、宏观时间序列 | 日频 / 周频 | 中高(部分免费) | 另类因子、宏观因子、学术数据集 |
| 交易所直连(CTP / MDGW) | 国内期货、股票 Level-2 | Tick 级实时 | 极高 | 高频做市、套利策略、极速交易 |
2.2 数据对齐与去重
多源数据在时间戳、股票代码格式、复权方式上存在差异,必须进行标准化对齐。对齐的核心原则是:以交易日历为基准,以统一代码映射为桥梁。
2.2.1 交易日历对齐
不同市场(A股、港股、美股)的交易日历不同,且同一市场内不同数据源可能因数据缺失产生非对齐时间戳。标准做法是维护一张交易日历表,将各数据源的时间序列左连接至该日历。
import pandas as pd
# 示例:交易日历对齐(A股)
trade_cal = pd.read_csv('trade_calendar.csv') # 包含所有交易日
price_data = pd.read_csv('stock_daily.csv') # 原始日频数据
# 左连接对齐
aligned = trade_cal.merge(price_data, on='trade_date', how='left')
aligned = aligned.sort_values(['stock_code', 'trade_date']).reset_index(drop=True)
2.2.2 代码映射与去重
同一只股票在不同数据源中可能使用不同代码(如 Wind 使用 000001.SZ,Tushare 使用 000001,Yahoo 使用 000001.SZ 或 000001.SHE)。需要建立统一的代码映射表,并在合并后按 (stock_code, trade_date) 去重,保留最新或最完整的一条记录。
# 代码映射示例
code_map = {
'000001': '000001.SZ',
'600000': '600000.SH'
}
# 去重:按股票+日期,保留最后一条
df = df.sort_values(['stock_code', 'trade_date', 'update_time'])
df = df.drop_duplicates(subset=['stock_code', 'trade_date'], keep='last')
drop_duplicates() 默认参数。金融数据中同一日期可能出现多次更新(如盘后修正、复权调整),应显式指定 keep='last' 并依据更新时间排序。
2.3 缺失值处理
金融时间序列的缺失值主要来源于停牌、数据源未收录、节假日差异等。处理策略需兼顾时序依赖性和因子计算需求。
2.3.1 向前填充(Forward Fill)
适用于价格类、市值类等具有记忆性的字段。停牌期间的价格应沿用最近一个交易日的收盘价,而非插入 0 或 NaN。
# 向前填充:按股票分组,填充停牌日缺失
df['close'] = df.groupby('stock_code')['close'].transform(lambda x: x.ffill())
2.3.2 插值法(Interpolation)
适用于收益率、波动率等短期变化平滑的指标。线性插值假设缺失值前后变化均匀;对于更复杂的曲线,可使用时间加权插值或样条插值。
# 线性插值:按股票分组,对缺失值进行线性插值
df['volume'] = df.groupby('stock_code')['volume'].transform(
lambda x: x.interpolate(method='linear', limit_direction='both')
)
# 时间加权插值(适用于不规则时间间隔)
df['turnover'] = df.groupby('stock_code')['turnover'].transform(
lambda x: x.interpolate(method='time')
)
| 缺失值处理方法 | 适用字段 | 优点 | 缺点 |
|---|---|---|---|
| 向前填充(ffill) | 收盘价、市值、PE | 保留时序记忆,符合停牌逻辑 | 连续缺失时误差累积 |
| 线性插值 | 成交量、换手率 | 平滑过渡,计算简单 | 假设线性变化,可能失真 |
| 时间加权插值 | 不规则时间序列 | 尊重时间间隔 | 计算开销较大 |
| 向后填充(bfill) | 极少使用 | 可用于尾部缺失 | 引入未来信息,回测中禁用 |
limit_direction='both'),因为它们会引入未来信息,导致因子表现虚高。仅在因子计算前的静态数据集清洗中可谨慎使用。
2.4 异常值检测与处理
金融数据中的异常值可能由数据录入错误、极端市场事件、复权计算错误等引起。异常值若不处理,会严重扭曲因子分布和后续模型训练。本节介绍两种稳健的检测方法。
2.4.1 MAD 法(Median Absolute Deviation)
MAD 法基于中位数和绝对中位差,对极端值不敏感,适合金融数据中常见的厚尾分布。判断标准:若观测值偏离中位数超过 n * MAD,则视为异常。
import numpy as np
def detect_outliers_mad(series, threshold=5):
median = series.median()
mad = np.median(np.abs(series - median))
# 避免 MAD 为 0 导致除零
if mad == 0:
return pd.Series([False] * len(series), index=series.index)
modified_z_score = 0.6745 * (series - median) / mad
return np.abs(modified_z_score) > threshold
# 示例:对日收益率进行 MAD 异常检测
returns = df.groupby('stock_code')['pct_chg'].transform(
lambda x: detect_outliers_mad(x, threshold=5)
)
df['is_outlier_mad'] = returns
2.4.2 分位数法(Quantile Method)
分位数法通过设定上下分位数阈值来识别极端值。常用 1% 和 99% 分位数作为截断点,或使用 IQR(四分位距)方法:[Q1 - 1.5*IQR, Q3 + 1.5*IQR] 之外视为异常。
def detect_outliers_iqr(series, multiplier=1.5):
q1 = series.quantile(0.25)
q3 = series.quantile(0.75)
iqr = q3 - q1
lower = q1 - multiplier * iqr
upper = q3 + multiplier * iqr
return (series < lower) | (series > upper)
# 示例:对市盈率进行 IQR 异常检测
pe_outliers = df.groupby('stock_code')['pe_ttm'].transform(
lambda x: detect_outliers_iqr(x, multiplier=2.0)
)
df['is_outlier_iqr'] = pe_outliers
| 方法 | 核心统计量 | 对极端值敏感度 | 推荐阈值 | 适用场景 |
|---|---|---|---|---|
| MAD 法 | 中位数 + 绝对中位差 | 低(稳健) | 3~5 倍 MAD | 收益率、波动率、换手率 |
| 分位数法(IQR) | Q1、Q3、IQR | 中等 | 1.5~3 倍 IQR | 估值指标(PE、PB)、市值 |
| 分位数法(固定分位) | 1% / 99% 分位数 | 高(硬截断) | 0.5%~99.5% | 因子标准化前的极端值截断 |
2.4.3 异常值处理策略
检测到异常值后,通常采用以下三种处理方式之一:
- 截断(Winsorize):将异常值替换为阈值边界值,保留样本量。
- 删除:直接剔除异常样本,适用于明显的数据录入错误。
- 填充为 NaN 后插值:先标记为缺失,再使用前文缺失值处理方法填充。
from scipy.stats.mstats import winsorize
# 截断处理:将超出 1% ~ 99% 分位数的值替换为边界值
df['pe_winsorized'] = df.groupby('trade_date')['pe_ttm'].transform(
lambda x: winsorize(x, limits=[0.01, 0.01])
)
2.5 数据清洗全流程 SVG 架构图
下图展示了从多源数据获取到清洗完成的完整流水线,包含对齐、去重、缺失值处理、异常值检测与处理四个核心环节。
2.6 异常值检测方法对比 SVG 图
下面对比 MAD 法与分位数法在检测效果上的差异,展示两种方法对同一组模拟收益率数据的异常标记结果。
2.7 本章小结
数据获取与清洗是因子特征工程中最基础也最容易被忽视的环节。本章覆盖了从数据源选型、对齐去重、缺失值处理到异常值检测的完整流程。核心要点包括:
- 数据源选择需平衡成本、覆盖范围与更新频率,个人与机构适用不同方案。
- 对齐与去重是保证因子计算一致性的前提,交易日历和代码映射是关键基础设施。
- 缺失值处理优先使用向前填充(价格类)或线性插值(比率类),回测中禁止引入未来信息。
- 异常值检测推荐 MAD 法与分位数法结合使用,按截面检测并采用截断或填充策略。
完成本章的数据清洗后,下一阶段将进入因子计算与特征构建环节。