4、订单流数据采集:交易所API对接
数据采集是整个订单流系统的「水源」。水源不干净,后面再好的分析引擎也是白搭。我在这个环节踩过的坑,比后面所有环节加起来都多。
说白了,交易所API对接就三件事:连得上、收得全、存得住。但每一件都有不少门道。
4.1 REST vs WebSocket:什么时候用哪个?
先说说这两个协议。很多人一上来就问「哪个更好」,其实不存在更好,只有更合适。
| 特性 | REST | WebSocket |
|---|---|---|
| 连接方式 | 请求-响应 | 长连接,全双工 |
| 延迟 | 50-200ms(含网络) | 1-10ms |
| 适用场景 | 查询账户、下单、获取历史数据 | 实时行情、订单簿增量 |
| 资源消耗 | 低(用完即断) | 高(需维持心跳) |
我个人习惯:REST做「查」和「写」,WebSocket做「收」。比如查询持仓余额用REST,订阅逐笔成交用WebSocket。这样分工明确,出了问题也好排查。
核心原则:REST负责「一次性操作」,WebSocket负责「持续订阅」。千万别用REST去轮询行情,那是在给自己挖坑。
4.2 实时行情数据订阅
行情订阅,说白了就是告诉交易所:「我要看哪些品种的哪些数据,有更新就推给我。」
以币安的WebSocket接口为例,订阅深度行情是这样的:
// 伪代码示例 - 订阅BTC/USDT的订单簿增量
ws.connect("wss://stream.binance.com:9443/ws")
ws.send({
"method": "SUBSCRIBE",
"params": [
"btcusdt@depth20@100ms", // 20档深度,100ms快照
"btcusdt@trade" // 逐笔成交
],
"id": 1
})
嗯,这里要注意:订阅的频道不是越多越好。我曾经有个项目,一口气订阅了50个交易对的所有频道,结果本地带宽被打满,消息处理队列直接爆了。后来我学乖了,只订阅真正需要的品种,而且分批次订阅。
我的经验:订阅数量控制在10个交易对以内,每个交易对最多3个频道(深度、成交、K线)。如果确实需要更多,考虑多开几个连接,分散压力。
4.3 订单簿增量更新处理
订单簿是高频交易的核心。全量快照动辄几百KB,每秒拉一次根本不现实。所以交易所都支持增量更新——只告诉你「变了什么」,而不是「全部是什么」。
增量更新的逻辑其实很简单:
- 获取初始快照:第一次连接时,拉一次全量订单簿
- 维护本地副本:在内存里维护一个OrderBook对象
- 应用增量:收到增量消息后,更新本地副本
- 校验一致性:每隔一段时间,用快照校验本地数据是否正确
我曾经遇到过一个经典问题:增量消息丢了,导致本地订单簿和交易所不一致。后来我加了一个「校验机制」——每1000条增量后,主动拉一次快照做对比。如果发现偏差,就重建本地订单簿。
避坑指南:增量消息的序号(sequence ID)一定要校验。如果发现序号不连续,立即丢弃当前增量,重新拉取全量快照。别问我怎么知道的——有一次我忽略了序号校验,结果策略根据错误的订单簿下了单,亏了六位数。
4.4 数据缓存策略
数据到了本地,不能直接扔给策略用。为什么?因为网络抖动、交易所限流、本地处理延迟,都会导致数据「断流」或「堆积」。缓存层就是用来平滑这些问题的。
我常用的缓存策略分三层:
| 层级 | 存储介质 | 用途 | 保留时间 |
|---|---|---|---|
| L1 | 内存(RingBuffer) | 最新行情,供策略实时消费 | 秒级 |
| L2 | Redis | 分钟级K线、订单簿快照 | 小时级 |
| L3 | 时序数据库(InfluxDB/TDengine) | 历史数据,用于回测和分析 | 永久 |
L1缓存我用的是RingBuffer,说白了就是一个固定大小的循环数组。写入O(1),读取O(1),没有GC压力。我习惯把RingBuffer大小设为2的幂次,比如65536,这样取模运算可以用位运算加速。
// RingBuffer 核心逻辑(伪代码)
class RingBuffer {
constructor(size) {
this.buffer = new Array(size)
this.mask = size - 1 // size必须是2的幂
this.writeIndex = 0
}
write(data) {
this.buffer[this.writeIndex & this.mask] = data
this.writeIndex++
}
read(fromIndex) {
return this.buffer[fromIndex & this.mask]
}
}
一个小技巧:L2缓存用Redis的Stream数据结构,天然支持消息持久化和消费者组。这样即使策略重启,也能从断点处继续消费,不会丢数据。
4.5 整体架构图
说了这么多,画张图把整个流程串起来。你想想看,数据从交易所出来,经过采集层、缓存层,最后到策略层,每一步都有它的职责。
这张图我画得比较简洁,但每个环节都有坑。比如采集层的「重连机制」,我见过有人用指数退避,结果交易所恢复后半小时才连上;也有人用固定间隔,结果把交易所网关打挂了。我个人习惯用「抖动退避」——基础间隔1秒,每次翻倍,再加一个随机偏移,既不会太快也不会太慢。
最后说一句:数据采集是整个系统的基石。这块做得稳,后面策略怎么写都顺手。这块偷懒了,后面天天修数据。我见过太多团队在采集层省功夫,最后花十倍时间填坑。嗯,别问我怎么知道的。