3、数据获取与预处理:金融数据源介绍、数据清洗、缺失值处理、时间序列对齐

在量化回测与灰度发布流程中,数据质量直接决定模型的有效性与策略的稳健性。本章系统讲解金融数据的获取来源、清洗规范、缺失值处理策略以及多源时间序列对齐方法,为后续 LightGBM 特征工程与回测奠定可靠的数据基础。

3.1 金融数据源介绍

量化策略开发通常依赖以下三类数据源,每类数据源在时效性、覆盖范围与成本上存在显著差异:

数据源类型 典型提供商 覆盖范围 更新频率 成本 适用场景
交易所直连 上交所、深交所、CME Level-2 逐笔成交、订单簿 实时 / 逐笔 极高 高频策略、做市
专业金融终端 Wind、Bloomberg、同花顺 股票、期货、期权、宏观 日频 / 分钟频 中高 中低频策略、研究
开源/免费数据 Yahoo Finance、Quandl、Tushare 主要市场日线、基本面 日频 / 周频 低 原型验证、教学
灰度发布数据选型建议: 灰度阶段建议优先使用专业终端或交易所直连数据,确保数据质量与回测结果的可复现性。开源数据仅用于快速原型验证,正式灰度前必须与权威数据源交叉校验。

3.2 数据清洗规范

原始金融数据通常包含以下问题,需按固定流程清洗:

  1. 重复值去除:同一时间戳下出现多条相同记录,保留第一条或最后一条。
  2. 异常值检测:价格、成交量出现明显偏离(如涨跌幅超过 20%、成交量突变为 0 或异常放大),需标记或剔除。
  3. 停牌/退市处理:停牌期间价格不变、成交量为 0,应保留但标记状态,避免参与模型训练。
  4. 复权处理:股票分红、送股、配股后需进行前复权或后复权,保证价格序列连续性。
# 数据清洗示例(Python 伪代码)
import pandas as pd

def clean_market_data(df: pd.DataFrame) -> pd.DataFrame:
    # 1. 去除重复行
    df = df.drop_duplicates(subset=['ts_code', 'trade_date'])
    # 2. 剔除异常价格(涨跌幅超过 20%)
    df = df[df['pct_chg'].between(-20, 20)]
    # 3. 标记停牌日
    df['is_suspended'] = (df['volume'] == 0) & (df['close'] == df['pre_close'])
    # 4. 前复权处理(使用复权因子)
    df['adj_close'] = df['close'] * df['adj_factor']
    return df
注意: 复权方式的选择会影响回测结果。前复权适合回测时模拟历史真实收益,后复权适合观察实际价格走势。灰度发布阶段建议统一使用前复权,并在回测报告中注明复权方式。

3.3 缺失值处理策略

金融时间序列的缺失值通常由停牌、数据源中断、节假日差异等原因造成。不同缺失模式需采用不同处理策略:

缺失模式 典型场景 推荐处理方法 注意事项
单点缺失 个别交易日数据缺失 前向填充(ffill) 仅适用于价格类,成交量不可填充
连续缺失 停牌 5 个交易日 标记为缺失,不填充 模型训练时需设置缺失值处理参数
跨品种缺失 A 股有数据,B 股无数据 对齐后删除该时间点 避免引入未来信息
节假日差异 中美市场节假日不同 按各自交易日独立对齐 回测时需使用统一日历
# 缺失值处理示例
# 前向填充价格,成交量不填充
price_cols = ['open', 'high', 'low', 'close', 'adj_close']
df[price_cols] = df[price_cols].fillna(method='ffill')
# 成交量缺失直接置为 0 或 NaN
df['volume'] = df['volume'].fillna(0)
# LightGBM 原生支持缺失值,无需额外插值
LightGBM 缺失值优势: LightGBM 原生支持缺失值处理,在训练时自动将缺失值分配到增益最大的分支,无需手动插值。但回测场景中,缺失值仍建议按业务逻辑处理,避免引入数据泄露。

3.4 时间序列对齐

多源数据(股票、指数、期货、宏观指标)通常具有不同的时间频率与交易日历,对齐是特征工程前的必要步骤。

3.4.1 对齐流程

  1. 统一交易日历:确定回测区间内所有品种的共同交易日。
  2. 频率统一:将分钟数据聚合为日线,或日线扩展为分钟(需谨慎)。
  3. 索引对齐:以主品种(如沪深 300 指数)的交易日为基准,左连接其他数据。
  4. 缺失标记:对齐后仍缺失的数据按 3.3 节策略处理。
# 时间序列对齐示例
import pandas as pd

# 假设 stock_df 为股票日线,index_df 为指数日线
# 以指数交易日为基准对齐
aligned_df = index_df[['trade_date', 'index_close']].merge(
    stock_df[['trade_date', 'close', 'volume']],
    on='trade_date',
    how='left'
)
# 对齐后检查缺失
print(aligned_df.isnull().sum())

3.4.2 对齐前后对比

下图展示了对齐前后数据结构的差异:

时间序列对齐前后对比 对齐前(多源数据时间戳不一致) 股票 A:2024-01-02 指数:2024-01-02 宏观:2024-01-03 股票 A:2024-01-03 指数:2024-01-03 宏观:2024-01-05 股票 A:2024-01-04 指数:2024-01-04 宏观:缺失 对齐 对齐后(统一交易日索引) 交易日:2024-01-02 股票 A 指数 宏观 交易日:2024-01-03 股票 A 指数 NaN 交易日:2024-01-04 股票 A 指数 NaN

3.5 数据预处理完整流程

将上述步骤整合为可复用的数据预处理管线:

def data_preprocessing_pipeline(raw_data_dir: str, output_dir: str):
    # 1. 加载原始数据
    stock_df = load_stock_data(raw_data_dir + '/stock/')
    index_df = load_index_data(raw_data_dir + '/index/')
    macro_df = load_macro_data(raw_data_dir + '/macro/')

    # 2. 清洗
    stock_df = clean_market_data(stock_df)
    index_df = clean_market_data(index_df)

    # 3. 缺失值处理
    stock_df = fill_missing_prices(stock_df)
    index_df = fill_missing_prices(index_df)

    # 4. 时间序列对齐
    aligned_df = align_by_trade_calendar(stock_df, index_df, macro_df)

    # 5. 保存预处理结果
    aligned_df.to_parquet(output_dir + '/aligned_data.parquet')
    print(f'预处理完成,共 {len(aligned_df)} 条记录')
灰度发布数据校验要点: 在灰度发布前,需对预处理后的数据做以下校验:
• 时间戳是否严格递增且无重复
• 价格序列是否连续(停牌日已标记)
• 对齐后缺失比例是否低于 5%
• 复权因子是否与官方数据一致

3.6 数据质量监控指标

建立数据质量监控看板,在灰度发布期间持续跟踪:

监控指标 计算方式 告警阈值 处理动作
缺失率 缺失值数量 / 总记录数 > 3% 检查数据源连接
异常价格占比 涨跌幅 > 20% 的记录数 / 总记录数 > 0.5% 标记并人工复核
对齐偏差 对齐后交易日数量与基准日历差异 > 2 天 检查交易日历配置
复权因子变化率 当日复权因子 / 前日复权因子 - 1 > 10% 确认分红送股事件

通过以上数据获取与预处理流程,可以为 LightGBM 回测灰度发布提供干净、对齐、可追溯的金融数据集,有效降低数据偏差带来的策略误判风险。