数据源接入与格式统一

做行情数据清洗,第一步就是跟各种数据源打交道。说实话,这步要是没做好,后面所有工作都是白搭。我见过太多团队,清洗逻辑写得天花乱坠,结果源头数据格式就乱了,最后全得返工。

今天咱们就聊聊,怎么把那些乱七八糟的数据源,统一成一套好用的标准格式。

常见行情数据源

行情数据从哪来?无非就三个路子:交易所API、第三方数据商、还有各种CSV文件。每种我都踩过坑,一个一个说。

交易所API

交易所API是最直接的来源。像Binance、Coinbase、OKX这些大所,都提供REST和WebSocket接口。

我个人习惯,实时行情用WebSocket,历史数据用REST。为什么?WebSocket是长连接,数据推得快,适合做实时监控。REST是一次性请求,适合拉取历史K线。

关键点:不同交易所的API返回格式完全不同。比如Binance的K线数据字段名是openTimeopenhigh,而OKX用的是tsoh。你想想看,要是直接存,后面查询时得多痛苦。

我的经验:建议在接入层就做一次格式转换。我曾在项目里写了个统一的API适配器,每个交易所实现一个适配器类,输出统一的数据结构。这样上游再怎么变,下游都不受影响。

第三方数据商

第三方数据商像彭博、路透、Wind这些,数据质量高,但贵。而且他们的数据格式往往更复杂。

我记得有一次,接一个第三方数据商的FIX协议数据。那家伙,字段名全是数字编号,比如55代表股票代码,44代表价格。没有文档根本看不懂。

注意:第三方数据商的数据通常有延迟。别指望他们能给你毫秒级的数据。做回测还好,做高频交易就得慎重了。

CSV文件

CSV文件是最古老也最通用的格式。很多量化团队的历史数据,都是CSV格式存的。

但CSV有个大问题——没有schema。你拿到一个CSV,根本不知道里面有哪些列,每列是什么类型。我见过最离谱的,同一个CSV文件里,日期列有时候是2024-01-01,有时候是01/01/2024,还有时候是20240101

我的建议:处理CSV时,一定要先做一次schema推断。用Pandas的read_csv时,指定dtype参数,别让它自动推断。自动推断有时候会翻车,比如把股票代码当成数字,前面的0就丢了。

数据格式标准化

数据源接进来了,接下来就是格式统一。常用的格式有三种:JSON、CSV、Parquet。各有各的适用场景。

格式 优点 缺点 适用场景
JSON 可读性好,结构灵活 体积大,解析慢 API交互、小数据量
CSV 通用性强,工具支持多 无schema,类型信息丢失 数据交换、历史数据存储
Parquet 压缩率高,查询快 可读性差,工具支持有限 大数据分析、列式存储

我个人建议,实时数据用JSON,历史数据用Parquet。CSV作为中间交换格式,但别把它当主力存储。

为什么?JSON是API的标准格式,解析方便。Parquet是列式存储,做聚合查询时快得飞起。CSV嘛,说白了就是兼容性好,但性能不行。

JSON标准化

JSON格式的标准化,主要是统一字段名和数据类型。比如所有时间戳都用Unix毫秒,所有价格都用字符串(避免浮点精度问题)。

// 标准化前的JSON
{
  "symbol": "BTCUSDT",
  "price": 50000.12,
  "time": "2024-01-01T00:00:00Z"
}

// 标准化后的JSON
{
  "symbol": "BTCUSDT",
  "price": "50000.12",
  "timestamp": 1704067200000
}

注意:价格用字符串存,不是矫情。浮点数在计算机里是近似值,0.1+0.2不等于0.3。做金融计算,一分钱都不能差。

CSV标准化

CSV标准化,核心是定义好列名和列顺序。我习惯用一套固定的列名模板,所有CSV都按这个模板输出。

# 标准CSV格式
symbol,timestamp,open,high,low,close,volume
BTCUSDT,1704067200000,50000.00,50100.00,49900.00,50050.00,1234.56

我的经验:CSV文件最好加上表头。没有表头的CSV,处理起来就是噩梦。我曾经接手过一个项目,CSV没表头,全靠猜列含义,最后发现有两列顺序搞反了,回测结果全错。

Parquet标准化

Parquet是二进制格式,没法直接看。但它的schema是自描述的,存的时候是什么类型,读出来就是什么类型。

# 用PyArrow定义Parquet schema
import pyarrow as pa

schema = pa.schema([
    pa.field("symbol", pa.string()),
    pa.field("timestamp", pa.int64()),
    pa.field("open", pa.decimal128(18, 8)),
    pa.field("high", pa.decimal128(18, 8)),
    pa.field("low", pa.decimal128(18, 8)),
    pa.field("close", pa.decimal128(18, 8)),
    pa.field("volume", pa.decimal128(18, 8))
])

注意:Parquet的decimal类型,精度和标度一定要统一。我见过有人用decimal(38, 0)存价格,结果小数点后面全丢了。嗯,这坑我踩过。

字段映射与对齐

字段映射,说白了就是把不同数据源的字段名,映射到统一的字段名上。比如Binance的openTime,映射成timestamp

字段对齐,是确保不同数据源的数据,在时间上是对齐的。比如两个交易所的1分钟K线,起始时间必须一致。

字段映射表

我习惯维护一张字段映射表,用YAML或者JSON存。这样新增数据源时,改配置就行,不用改代码。

# 字段映射配置
field_mapping:
  binance:
    openTime: timestamp
    open: open
    high: high
    low: low
    close: close
    volume: volume
  okx:
    ts: timestamp
    o: open
    h: high
    l: low
    c: close
    vol: volume

我的建议:字段映射表最好版本控制。每次修改都记录原因和时间。这样出了问题,能快速回滚。

时间对齐

时间对齐是行情数据清洗里最头疼的问题。不同交易所的K线起始时间可能不一样,有的从整点开始,有的从整分开始。

我常用的方法是,先统一时间基准。比如所有数据都按UTC时间,然后做重采样。

# 用Pandas做时间对齐
import pandas as pd

# 假设df是原始数据,时间戳列是timestamp
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)

# 重采样到1分钟K线
df_resampled = df.resample('1min').agg({
    'open': 'first',
    'high': 'max',
    'low': 'min',
    'close': 'last',
    'volume': 'sum'
})

关键点:重采样时要注意缺失值。如果某个时间窗口没有数据,是填前值还是填空?这取决于你的业务场景。做回测时,我一般填前值;做实时监控时,我填空。

知识体系总览

下面这张图,把数据源接入与格式统一的整体流程画出来了。你可以对照着看,心里有个谱。

数据源接入与格式统一流程 数据源层 交易所API 第三方数据商 CSV文件 格式标准化层 JSON标准化 CSV标准化 Parquet标准化 字段映射与对齐层 字段映射表(YAML/JSON) 时间对齐(重采样) 统一标准数据输出

你看,整个流程分四层:数据源层、格式标准化层、字段映射与对齐层、输出层。每一层解决一个核心问题。数据源层解决「从哪来」,格式标准化层解决「怎么存」,字段映射层解决「怎么对应」,输出层解决「怎么用」。

做这步时,别想着一步到位。先接一个数据源,跑通全流程,再慢慢加其他数据源。我见过太多人,一开始就想把所有数据源都接进来,结果搞了三个月还在对接。

最后说一句:数据源接入这块,慢就是快。把基础打牢了,后面清洗、分析、建模,都会顺畅很多。别问我怎么知道的,都是血泪教训。


公众号:蓝海资料掘金营,微信deep3321