2. 数据获取与清洗:金融数据源介绍与预处理

金融因子工程的第一步是获取可靠、对齐的原始数据。不同数据源在覆盖范围、更新频率、数据质量和获取成本上差异显著;而数据清洗(对齐、去重、缺失值处理、异常值检测)直接决定后续因子计算的有效性。本章系统梳理主流金融数据源,并给出标准化的清洗流程与代码实现。

2.1 主流金融数据源对比

量化研究常用的数据源可分为三类:商业终端、开源社区平台、以及交易所直连或第三方数据商。下表从数据覆盖、更新频率、成本与适用场景四个维度进行对比。

数据源 覆盖范围 更新频率 成本 典型适用场景
Wind(万得) A股、港股、美股、期货、债券、基金、宏观 实时 / 日频 / 分钟频 高(机构年费) 机构投研、高频因子、实时风控
Tushare Pro A股、基金、期货、指数、宏观 日频 / 分钟频(积分制) 中(积分付费) 个人量化、回测研究、因子实验室
Yahoo Finance 全球股票、ETF、指数、外汇、加密货币 日频 / 实时(延迟) 免费 全球多资产回测、学术研究、原型验证
Quandl / Nasdaq Data Link 美股、另类数据、宏观时间序列 日频 / 周频 中高(部分免费) 另类因子、宏观因子、学术数据集
交易所直连(CTP / MDGW) 国内期货、股票 Level-2 Tick 级实时 极高 高频做市、套利策略、极速交易
选型建议: 个人研究者优先使用 Tushare Pro 或 Yahoo Finance 进行因子原型开发;机构团队建议以 Wind 为主数据源,辅以交易所直连获取高频数据。避免在回测阶段混用不同数据源的历史数据,防止因复权方式或停牌处理不一致导致因子偏差。

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) 极少使用 可用于尾部缺失 引入未来信息,回测中禁用
回测禁忌: 在回测环境中,禁止使用向后填充(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])
)
实践要点: 异常值检测应按截面(同一交易日)进行,而非跨时间截面混合检测。因为不同时期的估值中枢和市场波动水平不同,混合检测会错误地将正常值标记为异常。MAD 法在收益率检测中表现优于 IQR 法,因为收益率分布更接近拉普拉斯分布而非正态分布。

2.5 数据清洗全流程 SVG 架构图

下图展示了从多源数据获取到清洗完成的完整流水线,包含对齐、去重、缺失值处理、异常值检测与处理四个核心环节。

数据清洗全流程流水线 Wind Tushare Yahoo Finance 其他数据源 多源数据合并与代码映射 交易日历对齐 + 按 (股票, 日期) 去重 缺失值处理:向前填充 / 插值法 异常值检测:MAD 法 / 分位数法 清洗完成 → 因子计算 关键原则 • 按截面(同一交易日)检测异常 • 回测中禁止使用未来信息填充 • 停牌数据使用向前填充 • 多源数据优先使用权威源

2.6 异常值检测方法对比 SVG 图

下面对比 MAD 法与分位数法在检测效果上的差异,展示两种方法对同一组模拟收益率数据的异常标记结果。

MAD 法 vs 分位数法(IQR)异常检测对比 MAD 法(稳健) 中位数 ±5×MAD 阈值带 • 基于中位数,对极端值不敏感 • 适合厚尾分布(收益率、波动率) • 推荐阈值:3~5 倍 MAD 分位数法(IQR) Q1 Q3 ±1.5×IQR 阈值带 • 基于四分位数,计算简单 • 适合估值指标(PE、PB) • 推荐倍数:1.5~3 倍 IQR 总结:MAD 法对极端值更稳健,适合收益率类因子;分位数法更直观,适合截面估值类因子。建议在因子工程中同时使用两种方法交叉验证。

2.7 本章小结

数据获取与清洗是因子特征工程中最基础也最容易被忽视的环节。本章覆盖了从数据源选型、对齐去重、缺失值处理到异常值检测的完整流程。核心要点包括:

  • 数据源选择需平衡成本、覆盖范围与更新频率,个人与机构适用不同方案。
  • 对齐与去重是保证因子计算一致性的前提,交易日历和代码映射是关键基础设施。
  • 缺失值处理优先使用向前填充(价格类)或线性插值(比率类),回测中禁止引入未来信息。
  • 异常值检测推荐 MAD 法与分位数法结合使用,按截面检测并采用截断或填充策略。

完成本章的数据清洗后,下一阶段将进入因子计算与特征构建环节。