第2章:数据层设计——市场数据接入
数据层,说白了就是量化交易系统的「血管」。没有数据,策略再牛也是空中楼阁。我做了这么多年量化系统,见过太多团队在策略上花了大把精力,结果数据接入环节出了幺蛾子,整个系统直接崩掉。今天咱们就来聊聊,怎么把这条「血管」搭得又稳又快。
2.1 行情源接入:多源异构的挑战
行情源这东西,你想想看,交易所、数据商、甚至一些免费接口,每家都有自己的格式。有的用二进制协议,有的用JSON,有的用FIX协议。我早期做的一个项目,接了5个行情源,结果每个源的字段命名都不一样——有的叫"last_price",有的叫"close",有的叫"trade_price"。嗯,这坑我踩过。
我个人习惯的做法是:统一抽象一个行情数据模型。不管底层是什么格式,到了我这层,必须转成统一结构。
// 统一行情数据结构示例
{
"symbol": "BTCUSDT", // 统一命名
"exchange": "binance", // 来源交易所
"timestamp": 1700000000, // 毫秒时间戳
"price": 50000.00, // 最新价
"volume": 123.45, // 成交量
"bid": 49999.00, // 买一价
"ask": 50001.00 // 卖一价
}
2.2 API网关:流量管控与协议转换
行情数据来了,不能直接往系统里灌。你得有个「门卫」——API网关。它干三件事:限流、鉴权、协议转换。
我记得有一次,某个行情源突然发了10倍于正常量的数据,要不是网关限流,下游的Redis直接被打满。网关的限流策略,我建议用令牌桶算法,简单又有效。
| 功能 | 说明 | 常用工具 |
|---|---|---|
| 限流 | 防止突发流量冲垮下游 | 令牌桶、漏桶 |
| 鉴权 | 验证数据源身份 | API Key、JWT |
| 协议转换 | 将不同协议统一为内部格式 | gRPC、WebSocket |
2.3 数据标准化与清洗:脏数据是毒药
行情数据里,脏数据太常见了。比如价格突然变成0,或者时间戳跳变。我做过一个回测系统,因为没做清洗,策略在历史数据上跑得飞起,实盘直接亏钱——后来发现是某天的数据里混了一笔「价格=0」的成交记录。
清洗规则,我一般定这几条:
- 空值处理:价格、成交量不能为null,否则丢弃该条
- 范围校验:价格不能为负,成交量不能为0
- 时间校验:时间戳不能是未来时间,也不能比上一笔还早
- 重复去重:同一时间戳、同一品种的重复数据,只保留一条
// 清洗逻辑伪代码
function cleanTick(tick) {
if (tick.price <= 0) return null;
if (tick.volume <= 0) return null;
if (tick.timestamp > now()) return null;
if (tick.timestamp < lastTimestamp) return null;
return tick;
}
2.4 数据缓存策略:Redis还是Memcached?
行情数据讲究一个「快」。你想想看,高频交易里,毫秒级的延迟都可能决定盈亏。所以,数据不能每次都去查数据库,得用缓存。
我个人习惯用Redis,原因有三:
- 支持丰富的数据结构(List、Sorted Set、Stream)
- 自带持久化(RDB/AOF),重启不丢数据
- 支持发布订阅,适合行情广播
Memcached呢?它更简单,纯内存,性能略高一点。但缺点也很明显——不支持持久化,重启全丢。我一般只在「丢了也无所谓」的场景用Memcached,比如临时计算中间结果。
| 特性 | Redis | Memcached |
|---|---|---|
| 数据结构 | 丰富(String/List/Set等) | 仅String |
| 持久化 | 支持 | 不支持 |
| 性能 | 高 | 极高 |
| 适用场景 | 行情缓存、订单簿、实时数据 | 简单KV缓存 |
2.5 数据持久化:时序数据库选型
缓存里的数据是「热数据」,过一会儿就得存到硬盘里做持久化。量化交易的数据,天然就是时间序列——每个数据点都带时间戳。所以,别用MySQL这种关系型数据库,性能扛不住。
时序数据库,我接触过几个:
- InfluxDB:老牌,功能全,但集群版收费
- TimescaleDB:基于PostgreSQL,SQL兼容性好,适合团队有SQL背景的
- ClickHouse:列式存储,查询极快,适合分析型场景
- DolphinDB:国产,专为金融设计,但学习成本高
我个人推荐ClickHouse。为什么?因为它查询速度快,而且支持SQL。我做过一个回测系统,用ClickHouse存储1年的1分钟K线数据,查询某只股票的历史行情,响应时间在100毫秒以内。InfluxDB也能做到,但SQL兼容性差一些,团队上手慢。
-- ClickHouse建表示例
CREATE TABLE market_data.kline_1min (
symbol String,
exchange String,
timestamp DateTime,
open Float64,
high Float64,
low Float64,
close Float64,
volume Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (symbol, timestamp);
2.6 整体数据流架构
说了这么多,咱们画个图看看整体数据流长什么样。
你看,数据从行情源进来,经过网关、清洗、缓存,最后落到持久化存储。每一层都有明确的职责。我见过一些团队,把清洗逻辑写在网关里,或者把缓存和持久化混在一起,结果出了问题都不知道该查哪一层。
公众号:蓝海资料掘金营,微信deep3321