第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           // 卖一价
}
我的经验:别信行情源说的「99.99%可用」。我遇到过某知名数据商在极端行情下直接断流。所以,至少准备2个独立行情源做冗余,一个主用,一个备用。切换逻辑要自动,别等人发现。

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缓存
避坑指南:Redis的过期策略要小心。我曾经设置了一个行情key的过期时间为60秒,结果行情波动大时,key提前过期,下游拿不到数据直接报错。后来我改用「主动过期+被动清理」结合的方式,才稳定下来。

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);
注意:时序数据库的「压缩」和「分区」策略很重要。我见过有人没设分区,结果一张表几百GB,查询慢得要命。按时间分区,比如按月或按周,能大幅提升查询性能。

2.6 整体数据流架构

说了这么多,咱们画个图看看整体数据流长什么样。

行情源A 行情源B API网关 限流/鉴权/协议转换 标准化清洗 去重/校验/格式化 Redis缓存 ClickHouse 图例: 行情源 API网关 清洗层 缓存 持久化

你看,数据从行情源进来,经过网关、清洗、缓存,最后落到持久化存储。每一层都有明确的职责。我见过一些团队,把清洗逻辑写在网关里,或者把缓存和持久化混在一起,结果出了问题都不知道该查哪一层。

总结一句话:数据层设计,核心就是「分层解耦」。每一层只做一件事,做好一件事。这样系统才扛得住极端行情,才经得起时间考验。

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