3、数据获取与预处理:数据源选择、数据清洗、数据对齐与重采样

做统计套利,数据就是你的命根子。这话一点都不夸张。

我见过太多人,策略逻辑写得天花乱坠,结果一跑回测,全是噪音。为什么?数据源没选对,或者预处理没做好。说白了,垃圾进垃圾出。今天咱们就把这块硬骨头啃下来。

3.1 数据源选择:Yahoo Finance vs 本地CSV

我个人习惯,做快速原型验证的时候,首选 Yahoo Finance。免费、方便、覆盖面广。但你要真拿它做生产环境,我劝你三思。

⚠️ 避坑指南: 我曾经在2021年用Yahoo Finance抓美股数据,结果某天API突然改了字段名,我的整个回测框架直接崩了。从那以后,我养成了一个习惯——永远在本地留一份CSV备份

两种数据源怎么选?我列个对比表,你一看就明白:

对比维度 Yahoo Finance 本地CSV
获取速度 依赖网络,有延迟 秒级加载
数据完整性 偶有缺失,节假日不统一 可自行校验,可控
历史深度 一般支持10-20年 取决于你存了多少
适用场景 快速验证、小规模回测 生产环境、高频策略

我的建议是:先用Yahoo Finance做快速迭代,策略稳定后,再切换到本地CSV做精细回测。这样既快又稳。

3.2 数据清洗:缺失值与异常值

拿到数据后,第一件事不是跑策略,而是洗数据。你想想看,如果数据里有个空值,你的协整检验直接报错,那多尴尬。

3.2.1 缺失值处理

缺失值一般有两种:一种是某一天完全没有数据(比如节假日),另一种是某一天部分字段缺失。

我常用的处理方式:

  • 向前填充(ffill):用上一个交易日的数据填充。适合价格数据,因为价格不会突变。
  • 线性插值:适合成交量等波动较大的指标。
  • 直接删除:如果缺失比例超过5%,我建议直接删掉那段时间的数据。
💡 一个小技巧: 对于统计套利,两个品种的缺失日期必须一致。如果A股某天停牌,B股正常交易,那这一天两个品种的数据都不能用。否则价差计算会失真。

3.2.2 异常值检测

异常值怎么找?我一般用两种方法:

  1. 3σ原则:超过均值±3倍标准差的数据,标记为异常。
  2. IQR方法:四分位距法,更稳健,不受极端值影响。

举个例子,某只股票突然一天涨了50%,但大盘没动。这大概率是数据错误,而不是真实行情。我曾经在回测中没处理这种异常值,结果策略信号全乱了,白白浪费了两天时间排查。

⚠️ 注意: 不要机械地删除所有异常值。有些异常是真实的市场事件(比如财报发布、分红除权)。你需要结合业务逻辑判断。

3.3 数据对齐与重采样

做统计套利,最核心的就是价差。价差怎么算?两个品种的价格相减。但前提是——时间必须对齐

你想想看,如果A股是9:30开盘,港股是9:00开盘,那9:15这个时间点,A股还没数据,港股已经有了。直接算价差?那肯定不对。

3.3.1 时间对齐

我常用的对齐策略:

  • 按分钟对齐:取两个品种都有数据的时间点。缺失的用前值填充。
  • 按日对齐:只取两个品种都交易的日期。节假日不一致的,直接跳过。

这里有个坑:不同交易所的休市时间不同。比如美国有感恩节,中国有春节。如果你做跨市场套利,一定要手动维护一个交易日历。

3.3.2 重采样

重采样,说白了就是把高频数据降成低频,或者把低频数据升成高频。

我一般这样做:

  • 日线数据:用收盘价,不需要重采样。
  • 分钟数据:如果需要做日内套利,我会把1分钟数据重采样成5分钟或15分钟。为什么?因为1分钟数据噪音太大,协整检验容易误判。
🔧 实操建议: 重采样时,价格用最后一条记录(收盘价),成交量用求和。这个规则在pandas里用 resample().agg() 很容易实现。

3.4 知识体系框架图

下面这张图,是我自己总结的数据预处理流程。你照着做,基本不会出大问题:

数据获取与预处理流程 数据源选择 Yahoo Finance 本地CSV 数据清洗 缺失值处理 异常值检测 对齐与重采样 最终输出:干净、对齐、统一频率的数据

3.5 代码实战:一个完整的预处理流程

光说不练假把式。我给你一个可以直接跑的代码片段,把上面讲的内容串起来:

import pandas as pd
import numpy as np
from pandas_datareader import data as pdr
import yfinance as yf

# 1. 获取数据
yf.pdr_override()
tickers = ['AAPL', 'MSFT']
data = pdr.get_data_yahoo(tickers, start='2020-01-01', end='2023-12-31')

# 2. 提取收盘价
close_prices = data['Close']

# 3. 缺失值处理:向前填充
close_prices = close_prices.fillna(method='ffill')

# 4. 异常值检测:3σ原则
def detect_outliers(series):
    mean = series.mean()
    std = series.std()
    return (series < mean - 3*std) | (series > mean + 3*std)

outliers = close_prices.apply(detect_outliers)
# 将异常值替换为前值
close_prices[outliers] = close_prices.shift(1)[outliers]

# 5. 数据对齐:只保留两个品种都有数据的日期
close_prices = close_prices.dropna()

# 6. 重采样:如果需要分钟数据,可以这样
# close_prices.resample('5T').last()

print(f"最终数据形状: {close_prices.shape}")
print(f"缺失值数量: {close_prices.isnull().sum().sum()}")
💡 个人经验: 这段代码我用了快三年,基本没出过问题。唯一要注意的是,fillna(method='ffill') 在数据开头有缺失时会失效。所以我会在填充前先检查前几行数据。

嗯,数据预处理这块,说白了就是「慢工出细活」。你花80%的时间洗数据,剩下20%的时间跑策略,这才是正确的比例。别急着上模型,先把数据搞干净。


公众号:蓝海数据掘金营,微信deep3321