数据准备:高频数据获取与清洗

做流动性分析,数据是地基。地基不稳,楼盖得再漂亮也得塌。

我个人习惯把数据准备分成三大块:获取、清洗、存储。这三块环环相扣,哪一步出问题,后面的分析全白搭。今天咱们就聊聊,怎么把这套流程跑通。

核心观点:高频数据的质量,直接决定流动性指标的可靠性。宁可花80%的时间在数据准备上,也别急着跑模型。

一、高频数据获取:Tick级与分钟级

先说说数据源。国内常用的有Wind、聚宽、Tushare,国外有Quandl、Polygon。我建议你至少准备两个数据源做交叉验证。

1. Tick级数据

Tick数据就是每一笔成交的记录。包含时间戳、价格、成交量、买卖方向。这东西有多大?

举个例子:沪深300成分股,一天下来大概产生500万到1000万条Tick记录。一个月就是2个亿的量级。嗯,存储压力不小。

# 获取Tick数据的伪代码示例
def fetch_tick_data(symbol, date):
    """
    从交易所获取Tick级数据
    """
    # 连接数据源
    conn = create_connection(source='wind')
    
    # 获取原始Tick数据
    raw_ticks = conn.query(f"""
        SELECT time, price, volume, side
        FROM tick_table
        WHERE symbol = '{symbol}'
        AND trade_date = '{date}'
        ORDER BY time
    """)
    
    return raw_ticks

我的经验:获取Tick数据时,一定要注意时间戳的精度。有些数据源给的是毫秒级,有些是微秒级。我遇到过数据源混用精度的情况,结果算出来的流动性指标全是错的。

2. 分钟级数据

分钟级数据其实是Tick数据的聚合。把1分钟内的所有Tick按时间、价格、成交量汇总。常用的有1分钟、5分钟、15分钟、30分钟。

# Tick转分钟数据的聚合逻辑
def aggregate_to_minute(ticks):
    """
    将Tick数据聚合成分钟级数据
    """
    # 按分钟分组
    ticks['minute'] = ticks['time'].dt.floor('1min')
    
    # 聚合计算
    minute_data = ticks.groupby('minute').agg({
        'price': ['first', 'last', 'max', 'min', 'mean'],
        'volume': 'sum',
        'side': lambda x: (x == 'buy').sum()  # 买方成交笔数
    })
    
    return minute_data

为什么要两种粒度都保留?说白了,不同场景用不同数据。做日内高频策略,Tick级是必须的。做日间分析,分钟级就够了。你想想看,如果每次跑回测都用Tick数据,那计算量得多大?

二、数据清洗与对齐

数据拿到手,别急着用。先洗一洗。我在项目中遇到过太多坑了——空值、异常值、时间戳错位,哪个都能让你白忙活半天。

1. 常见的数据问题

问题类型 表现 处理方法
缺失值 某段时间没有交易记录 前向填充或插值
异常值 价格突然跳变(比如从10块跳到100块) 设置阈值过滤(比如超过3个标准差)
时间戳错位 不同数据源的时间基准不一致 统一转换为UTC时间
重复记录 同一笔交易被记录了两次 按时间戳去重

避坑指南:我曾经处理过一批数据,发现某只股票在下午3点后还有大量交易记录。查了半天才发现,是数据源把盘后大宗交易也混进来了。记住:A股收盘后15分钟内的大宗交易,要单独处理。

2. 数据对齐

做流动性分析,经常需要把不同频率的数据对齐。比如Tick数据和分钟数据,怎么对齐?

# 数据对齐示例
def align_data(tick_data, minute_data):
    """
    将Tick数据对齐到分钟数据的时间轴上
    """
    # 将Tick数据的时间戳向下取整到分钟
    tick_data['aligned_time'] = tick_data['time'].dt.floor('1min')
    
    # 按对齐后的时间合并
    aligned = pd.merge_asof(
        minute_data.sort_values('time'),
        tick_data.sort_values('aligned_time'),
        left_on='time',
        right_on='aligned_time',
        direction='nearest'
    )
    
    return aligned

这里有个细节要注意:对齐方向。你是用前向对齐还是后向对齐?我个人习惯用前向对齐——用上一个有效数据填充当前缺失值。为什么?因为交易数据天然有滞后性,用未来的数据填充当前,会引入未来信息。

三、数据存储方案

高频数据量太大,普通的关系型数据库扛不住。我建议用列式存储或者时序数据库

1. 存储方案对比

方案 优点 缺点 适用场景
Parquet + HDF5 压缩率高,读取快 不支持实时写入 离线分析
InfluxDB 支持实时写入,查询快 存储成本高 实时监控
ClickHouse 列式存储,聚合查询极快 运维复杂 大规模分析
MongoDB 灵活,支持文档结构 查询性能一般 原型开发

我的建议:如果你刚开始做,先用Parquet格式存本地。等数据量上来了,再迁移到ClickHouse。别一上来就搞分布式,运维成本太高。

2. 存储结构设计

我习惯按日期+股票代码分片存储。这样查询某只股票某天的数据,直接定位到对应文件,不用全表扫描。

# 存储目录结构示例
/data/
  /ticks/
    /2024-01-01/
      000001.SZ.parquet
      000002.SZ.parquet
      ...
    /2024-01-02/
      ...
  /minutes/
    /2024-01-01/
      000001.SZ.parquet
      ...
    /2024-01-02/
      ...

为什么要按日期分?因为流动性分析通常按天做。你想想看,如果所有数据都塞在一个文件里,查某一天的数据得读整个文件,那效率得多低?

四、知识体系总览

下面这张图,把整个数据准备的流程串起来了。你可以把它当作一个检查清单——做每一步之前,先看看自己走到哪了。

高频数据准备流程 第一阶段:数据获取 Tick级数据 分钟级数据 多数据源交叉验证 每笔成交记录 1min/5min/15min聚合 Wind/聚宽/Tushare 第二阶段:数据清洗 缺失值处理 异常值过滤 时间戳对齐 前向填充/插值 3σ阈值/业务规则 统一UTC/前向对齐 第三阶段:数据存储 Parquet/HDF5 InfluxDB/ClickHouse 按日期+代码分片 离线分析首选 实时监控/大规模分析 查询效率提升10倍+ 数据质量 = 分析结果的可靠性

这张图把整个流程分成了三个阶段。每个阶段都有对应的技术选型和注意事项。你照着这个框架走,基本不会出大问题。

最后说一句:数据准备这件事,没有捷径。我见过太多人急着跑模型,结果数据没洗干净,出来的结论全是错的。记住:垃圾进,垃圾出。把数据准备这一步做扎实了,后面的分析才能站得住脚。

专注资料整理