3、数据源接入与预处理:对接Tushare/Wind/本地CSV数据,数据清洗、对齐与标准化

做量化回测,第一步就是搞数据。

数据搞不定,后面全是白搭。我见过太多人,策略写得漂漂亮亮,一跑回测就崩,最后发现是数据没对齐。嗯,这章咱们就把数据源接入和预处理这件事聊透。

3.1 数据源的选择:Tushare、Wind、本地CSV

我个人习惯把数据源分成三类:免费开源、商业付费、本地文件。各有各的适用场景。

数据源 特点 适用场景
Tushare Pro 免费但需积分,覆盖A股/期货/基金 个人研究、小规模回测
Wind 收费,数据质量高,接口稳定 机构级、生产环境
本地CSV 灵活,可离线,格式自定 历史数据回放、自定义数据

你想想看,如果只是自己写写策略玩,Tushare完全够用。但要是跑实盘或者做高频分析,Wind的稳定性和数据精度就很重要了。我曾经在项目中遇到过Tushare某天接口超时,导致整个回测任务中断的情况——从那以后,我就在代码里加了重试机制和本地缓存。

3.2 数据清洗:脏数据是回测的隐形杀手

数据拿到手,第一件事不是分析,是清洗。说白了,就是要把那些「不对劲」的数据揪出来。

常见的脏数据有这几类:

  • 缺失值:某天停牌,价格字段为空
  • 异常值:价格突然跳涨100倍,明显是数据录入错误
  • 重复数据:同一时间戳出现多条记录
  • 未来数据:时间戳错乱,把未来的数据混进来了

我一般这样处理:

import pandas as pd

def clean_market_data(df):
    # 1. 去重
    df = df.drop_duplicates(subset=['trade_date', 'ts_code'])
    
    # 2. 处理缺失值:前向填充
    df = df.sort_values('trade_date')
    df['close'] = df['close'].fillna(method='ffill')
    
    # 3. 剔除异常值:超过5个标准差
    mean = df['close'].mean()
    std = df['close'].std()
    df = df[(df['close'] > mean - 5*std) & (df['close'] < mean + 5*std)]
    
    # 4. 检查时间顺序
    df = df[df['trade_date'] <= pd.Timestamp.today()]
    
    return df
⚠️ 注意:前向填充(ffill)只适用于停牌等短期缺失。如果连续缺失超过5天,我建议直接删除该时间段,否则会引入偏差。

3.3 数据对齐:不同频率、不同品种的「时间配准」

做多因子回测时,最头疼的就是数据对齐。你想想看,股票A是日频数据,股票B是分钟频数据,指数又是另一个频率——怎么把它们拉到同一个时间轴上?

我常用的对齐策略:

  • 降频对齐:把高频数据聚合到低频(比如分钟转日线)
  • 重采样对齐:用resample方法统一时间戳
  • 交叉对齐:取所有数据源的时间戳并集,缺失的用NaN填充
def align_multi_assets(data_dict, freq='D'):
    """
    data_dict: {'000001.SZ': df1, '600000.SH': df2, ...}
    freq: 'D' 日频, 'W' 周频, 'M' 月频
    """
    aligned = []
    for code, df in data_dict.items():
        df = df.set_index('trade_date')
        # 重采样到统一频率
        df = df.resample(freq).last()
        df['ts_code'] = code
        aligned.append(df)
    
    # 合并所有资产
    result = pd.concat(aligned).reset_index()
    return result.pivot(index='trade_date', columns='ts_code', values='close')

这里有个坑:不同交易所的交易日历不一样。A股有春节、国庆,港股有圣诞,美股有感恩节。我曾经因为没处理交易日历差异,导致回测结果里出现了「在A股休市日交易」的荒谬情况。嗯,后来我专门维护了一份交易日历表。

3.4 数据标准化:让不同量纲的数据「可比较」

标准化不是为了好看,是为了让模型能正确学习。你想想看,如果价格是几百块,成交量是几百万手,这两个数值直接丢进模型,价格那列基本就被忽略了。

常用的标准化方法:

  • Z-score标准化:(x - mean) / std,适合正态分布数据
  • Min-Max归一化:(x - min) / (max - min),把数据压到[0,1]区间
  • 对数变换:log(x),适合右偏分布(比如收益率)
def standardize_features(df, method='zscore'):
    if method == 'zscore':
        return (df - df.mean()) / df.std()
    elif method == 'minmax':
        return (df - df.min()) / (df.max() - df.min())
    elif method == 'log':
        return np.log(df - df.min() + 1)
💡 个人建议:做因子分析时,我习惯用Z-score。因为很多统计模型假设数据服从正态分布,Z-score能保留数据的分布形态。Min-Max更适合深度学习模型,比如LSTM。

3.5 数据流水线:从原始数据到可用特征

在实际项目中,我不会手动一步步操作。我会搭一个数据流水线(Pipeline),把清洗、对齐、标准化串起来。

下面是我常用的流程:

class DataPipeline:
    def __init__(self, sources):
        self.sources = sources  # ['tushare', 'wind', 'csv']
        self.raw_data = {}
        self.clean_data = {}
    
    def fetch(self, codes, start, end):
        for source in self.sources:
            if source == 'tushare':
                self.raw_data.update(self._fetch_tushare(codes, start, end))
            elif source == 'wind':
                self.raw_data.update(self._fetch_wind(codes, start, end))
            elif source == 'csv':
                self.raw_data.update(self._fetch_csv(codes, start, end))
    
    def process(self):
        for code, df in self.raw_data.items():
            df = clean_market_data(df)
            df = align_multi_assets({code: df})
            df = standardize_features(df)
            self.clean_data[code] = df
        return self.clean_data

这样做的好处是:哪天想换数据源,或者加一个新的预处理步骤,只需要改Pipeline里的一个方法就行。我在项目中用过这个模式,维护成本低很多。

3.6 避坑指南:数据预处理中的常见陷阱

做数据预处理这些年,我踩过的坑不少。挑几个典型的说说:

  • 幸存者偏差:只用了当前还在交易的股票,退市的股票数据全丢了。回测结果会虚高。解决办法:用历史快照数据,或者包含退市股票。
  • 前视偏差:用未来的数据来填充过去的缺失值。比如用明天的收盘价来填充今天的NaN。这会导致回测结果「未卜先知」。
  • 除权除息未处理:股票分红送股后,价格会跳空。如果不做复权处理,技术指标全乱套。我一般用后复权数据做回测。
🔑 核心原则:数据预处理的目标不是让数据「好看」,而是让数据「真实」。回测时你用的数据,必须和实盘时能拿到的数据完全一致。任何「美化」都是在欺骗自己。

3.7 本章知识体系

下面这张图,是我对数据预处理流程的总结。你可以把它当作一个检查清单:

数据预处理流程 数据源接入 Tushare / Wind / CSV 数据清洗 去重 / 缺失值 / 异常值 数据对齐 降频 / 重采样 / 交叉对齐 数据标准化 Z-score / Min-Max / 对数 数据流水线 Pipeline 自动化 可用特征数据 无偏、对齐、标准化的回测数据 ⚠️ 常见陷阱 幸存者偏差 / 前视偏差 数据预处理是回测的基石,每一步都不可跳过

数据预处理这件事,说白了就是「垃圾进,垃圾出」。你花80%的时间在数据上,剩下20%的时间写策略,这才是正确的比例。别想着跳过这一步,我试过,结果就是回测曲线漂亮得像假的一样——嗯,它确实是假的。


蓝海数据掘金营,专注资料整理