第三章:数据获取与处理

做T0交易,说白了就是跟市场抢时间。你策略再牛,数据跟不上,一切都是白搭。我个人习惯把数据层比作「地基」——地基不稳,楼盖得再高也得塌。

这一章,咱们就聊聊怎么打好这个地基。从Level-2行情获取,到数据清洗,再到构建Tick级数据表,最后落地存储。每一步我都会把实战中踩过的坑、用过的技巧分享出来。

3.1 Level-2行情数据获取

先说说数据源。A股的Level-2行情,目前主流渠道就那么几家:券商、第三方数据商(比如万得、聚宽、恒生),还有交易所直连。我个人建议,起步阶段用券商的Level-2接口就够了,成本低,接入也快。

为什么非要用Level-2?普通行情3秒一笔,Level-2是逐笔成交,3秒一次快照。你想想看,T0交易拼的就是毫秒级反应,用普通行情做T0,就像用望远镜打靶——看得见,打不准。

核心数据字段

  • 逐笔成交:成交时间、价格、数量、买卖方向
  • 十档行情:买一到买十、卖一到卖十的挂单量和价格
  • 委托队列:前50笔委托的明细
  • 快照数据:每3秒一次的全市场快照

我在项目中遇到过一个问题:券商接口返回的数据格式五花八门。有的用JSON,有的用Protobuf,还有的直接给你二进制流。嗯,这里要注意,一定要先做格式统一,不然后面处理起来会疯掉。

3.2 数据清洗与预处理

数据拿到手,别急着用。Level-2行情里「脏数据」不少,我总结了几类常见问题:

  • 时间戳错乱:网络延迟导致数据包乱序,后发的先到
  • 重复数据:同一个成交记录被推送了两次
  • 异常值:价格突然变成0,或者成交量暴增100倍
  • 缺失数据:某个时间窗口的数据没收到

我曾经因为没处理好重复数据,导致策略在回测里年化收益50%,实盘一跑直接亏钱。后来排查了两天才发现,是重复的成交记录让策略误以为流动性很好,频繁开仓。

避坑指南:数据清洗一定要做「去重+排序+校验」三步走。我曾经偷懒只做了去重,结果时间戳乱序导致策略信号全部错位,那叫一个惨。

清洗流程我一般这么设计:

  1. 按时间戳排序,丢弃明显异常的数据包
  2. 对同一毫秒内的重复数据,保留第一条
  3. 价格校验:超出当日涨跌停板的价格直接丢弃
  4. 成交量校验:单笔成交超过流通盘0.1%的,标记为异常

3.3 构建Tick级数据表

清洗完的数据,需要组织成结构化的Tick表。我习惯用Pandas的DataFrame来存,结构大概是这样的:

字段名 类型 说明
ts datetime64[ns] 时间戳,精确到毫秒
code string 股票代码
price float64 成交价格
volume int64 成交量(股)
side int8 买卖方向:1买 -1卖
bid_px_1 float64 买一价
ask_px_1 float64 卖一价

代码实现其实不复杂,核心逻辑就几行:

import pandas as pd
import numpy as np

def build_tick_table(raw_data):
    # 原始数据解析
    df = pd.DataFrame(raw_data)
    
    # 时间戳处理
    df['ts'] = pd.to_datetime(df['timestamp'], unit='ms')
    
    # 排序去重
    df = df.sort_values('ts').drop_duplicates(subset=['ts', 'code'])
    
    # 价格校验
    df = df[(df['price'] > 0) & (df['price'] < 1000)]
    
    return df

你想想看,一个股票一天大概产生几万笔成交,全市场几千只股票,数据量其实不小。所以Tick表的设计要兼顾查询效率和存储成本。我个人习惯把每只股票的数据单独存一个文件,按日期分目录,这样查询时只需要加载少量数据。

3.4 数据存储方案

存储方案这块,我踩过不少坑。最开始用CSV存,结果一个月的Tick数据就有几十个G,查询慢得要命。后来试过MySQL,写入速度跟不上。最后选定了Parquet格式,压缩率高,查询也快。

我的推荐方案

  • 实时数据:用Redis缓存,TTL设5分钟
  • 历史数据:用Parquet文件存储,按日期分区
  • 高频查询:用DuckDB做本地分析,比Pandas快10倍

存储目录结构我一般这么组织:

data/
├── 2024/
│   ├── 01/
│   │   ├── 600519.parquet
│   │   ├── 000001.parquet
│   │   └── ...
│   ├── 02/
│   └── ...
└── realtime/
    ├── 600519.parquet
    └── ...

写入代码示例:

def save_tick_data(df, code, date):
    path = f'data/{date[:4]}/{date[5:7]}/{code}.parquet'
    df.to_parquet(path, compression='snappy')

读取时,我一般只加载当天数据,配合DuckDB做聚合查询,速度非常快。嗯,这里要注意,Parquet文件不支持追加写入,所以实时数据要攒够一批再写,比如每5秒批量写入一次。

3.5 本章知识体系

为了让你更直观地理解整个数据流程,我画了一张图:

数据获取与处理流程 Level-2行情源 数据清洗 去重·排序·校验 Tick级数据表 结构化存储 存储 存储方案分支 实时数据 → Redis 历史数据 → Parquet 高频查询 → DuckDB

这张图把整个流程串起来了:从行情源出发,经过清洗、建表,最后落地到不同的存储层。每个环节都有坑,但只要你按这个流程走,基本不会出大问题。

好了,数据获取与处理这块就聊到这儿。记住一句话:数据质量决定策略上限。你花80%的时间在数据处理上,一点都不亏。


专注资料整理