3. 数据获取与预处理

数据获取与预处理 学习卡片

配对筛选与历史回测的可靠性,首先取决于数据质量。无论后续使用协整检验、距离法还是相关性排序,只要价格序列存在复权错误、时间错位或异常跳变,统计量就会被系统性扭曲,回测结果也会出现虚假的盈利或亏损。本节围绕数据源选择、复权处理、缺失值与异常值处理、时间对齐与频率转换四个环节,给出可落地的工程方法。

一、核心原理:数据质量决定配对有效性

配对交易的核心假设是两只证券之间存在长期均衡关系。若数据本身存在以下问题,均衡关系会被破坏:

  • 复权不一致:一只用前复权、另一只用不复权,除权日会出现人为价差,协整检验可能误判为“偏离”。
  • 时间错位:两只证券的交易日历不同(如A股与港股),直接按行对齐会把不同日期的价格放在同一行。
  • 缺失值:停牌、涨跌停或数据源断档导致某日无价格,若简单向前填充,会人为制造零收益,压低波动率。
  • 异常值:错误报价(如价格多一个零)会瞬间拉大价差,触发错误开仓信号。

核心原则:配对筛选使用的价格序列,必须在同一复权口径、同一时间轴、同一频率下可比。任何“先对齐再复权”或“先回测再清洗”的做法都会引入前视偏差或数据泄漏。

二、数据源选择:按可用性与成本分层

证券数据源可分为三类,选择时需权衡覆盖范围、历史长度、复权因子完整性和调用限制。

数据源类型 典型代表 优点 局限 适用场景
开源社区接口 AkShare、Tushare(免费版) 免费、上手快、覆盖A股/港股/ 稳定性一般、复权因子偶有缺失、频率受限 教学、原型验证、小规模回测
商业数据终端 Wind、Choice、聚宽 复权准确、历史长、字段全 需付费、API有配额 实盘研究、机构级回测
交易所/官方文件 交易所日线文件、SEC EDGAR 权威、无第三方加工误差 需自行解析、复权需自算 合规要求高、审计追溯

选择建议:教学与快速验证优先使用 AkShare 或 Tushare,但必须检查复权字段是否完整;若回测跨越除权除息日,应优先选择提供后复权或复权因子的数据源。

三、复权处理:前复权、后复权与复权因子

复权解决的是除权除息导致的价格跳空问题。常见口径有三种:

  • 不复权:保留原始成交价,除权日会出现向下跳空,不适合计算收益率。
  • 前复权:以当前价格为基准向前调整,历史价格被缩放。优点是当前价格真实,缺点是历史价格会随最新除权变化,回测中若使用未来复权因子会引入前视偏差。
  • 后复权:以上市首日为基准向后调整,历史价格固定,适合回测。缺点是当前价格与市场报价不一致。

回测建议:配对筛选与回测统一使用后复权价格,或使用复权因子自行计算。若数据源只提供前复权,应在每个回测截面上重新获取,避免用全历史前复权数据回测早期区间。

以下示例展示如何用 Python 对价格序列进行后复权校验:

import pandas as pd

# 假设 df 包含 date, close, factor(复权因子)
df = df.sort_values("date").reset_index(drop=True)
df["adj_close"] = df["close"] * df["factor"]

# 检查复权后收益率是否连续
df["ret"] = df["adj_close"].pct_change()
jump = df[df["ret"].abs() > 0.2]
print("异常跳变日期:")
print(jump[["date", "close", "adj_close", "ret"]])

若发现除权日仍有超过阈值的跳变,说明复权因子未正确应用或数据源本身有误,需要回退到交易所公告核对。

四、缺失值与异常值处理

缺失值常见于停牌、节假日错配和接口断档。处理策略取决于缺失原因:

  1. 停牌:不应简单填充。若停牌期间无法交易,回测中应跳过该标的或标记为不可交易。
  2. 节假日错配:属于时间对齐问题,应通过交易日历对齐,而非填充。
  3. 接口断档:可短期向前填充,但需限制连续填充天数(如不超过3天),并记录填充标记。

异常值检测常用方法包括:

  • 收益率阈值法:单日收益率绝对值超过20%(A股涨跌停限制下)视为可疑。
  • MAD法:基于中位数绝对偏差,对厚尾分布更稳健。
  • 价格量级检查:价格突然变为原来的10倍或0.1倍,通常是小数点错误。
import numpy as np

def detect_outliers(series, window=20, n_mad=5):
    median = series.rolling(window).median()
    mad = (series - median).abs().rolling(window).median()
    return (series - median).abs() > n_mad * mad

mask = detect_outliers(df["adj_close"])
print(df.loc[mask, ["date", "adj_close"]])

误区:直接删除异常值会导致时间轴断裂,影响后续滚动窗口计算。正确做法是先将异常值标记为缺失,再按缺失值策略处理,或使用中位数替换并保留标记列。

五、时间对齐与频率转换

配对的两只证券可能在不同交易所上市,交易日历不同。时间对齐的目标是生成一个共同的时间轴,使每个时间点上的价格都真实可交易。

常用对齐方式:

  • 内连接:只保留两者都有交易的日期,适合严格配对回测。
  • 外连接+前向填充:保留所有日期,缺失处用前值填充,但需限制填充天数。
  • 交易日历对齐:使用交易所日历生成基准轴,再分别映射。

频率转换方面,日线转周线时,应使用“最后有效价格”而非简单重采样均值,因为均值会平滑掉真实的收盘价。成交量则应使用求和。

# 日线转周线:价格取最后值,成交量求和
weekly = df.resample("W-FRI").agg({
    "adj_close": "last",
    "volume": "sum"
}).dropna()

# 两只证券按内连接对齐
aligned = pd.merge(
    df_a[["date", "adj_close"]],
    df_b[["date", "adj_close"]],
    on="date",
    how="inner",
    suffixes=("_a", "_b")
)
print("对齐后交易日数量:", len(aligned))

检查清单:完成预处理后,至少验证以下四项:①两只证券复权口径一致;②对齐后交易日数量与预期相符;③无连续超过3天的填充值;④收益率序列无超过涨跌停限制的异常跳变。

六、案例复盘:一次因复权错误导致的虚假配对

某研究者在筛选银行股配对时,使用前复权数据计算价差。由于其中一只银行在回测区间内发生多次分红,前复权因子随最新数据变化,导致早期价格被系统性压低。协整检验显示两者高度相关,但回测中每次除权日附近都出现虚假开仓信号,最终收益被交易成本吞噬。

复盘结论:配对筛选前必须固定复权口径,回测区间内使用后复权或固定复权因子;若数据源只提供前复权,应在每个回测截面上重新获取数据,避免未来函数。

数据预处理不是回测前的“一次性清洗”,而是贯穿研究流程的质量控制。只有确保价格序列在复权、时间、频率三个维度上可比,后续的配对筛选与回测才有意义。