4、数据源接入:交易所API介绍(REST vs WebSocket)、行情数据格式(Level1/Level2/Level3)、数据订阅与推送、数据缓存策略

做高频量化,第一关就是搞定数据源。

你策略再牛,模型再准,拿不到一手数据,全是白搭。我见过太多人,策略回测跑得飞起,一上实盘就崩,十有八九是数据源这块没处理好。

今天咱们就把这块彻底讲透。

4.1 交易所API:REST vs WebSocket

交易所给你数据,通常就两种方式:REST和WebSocket。说白了,一个是你主动去问,一个是交易所主动推给你。

4.1.1 REST API

REST就像你去图书馆借书。你问一句“有没有《量化交易实战》这本书?”,图书管理员查一下,告诉你“有,在第三排”。你问一次,他答一次。

在交易里,REST API就是发一个HTTP请求,交易所给你返回数据。比如查账户余额、查历史K线、下单。

特点:
  • 请求-响应模式,你主动发起
  • 适合低频操作,比如查账户、查历史数据
  • 有延迟,一次请求几十到几百毫秒
  • 有频率限制,比如每秒最多5次

我个人习惯,REST只用来做三件事:获取历史数据、查询账户状态、执行交易指令。行情订阅?千万别用REST,太慢了。

4.1.2 WebSocket API

WebSocket就完全不一样了。它像你打开电视看新闻。电视台一直播,你一直看。你不用每秒钟问一次“现在有什么新闻?”,电视自己会播。

交易所通过WebSocket把行情数据实时推给你。你只需要订阅一次,后面数据就源源不断流过来。

特点:
  • 推送模式,交易所主动发数据
  • 延迟极低,毫秒级甚至微秒级
  • 适合高频行情订阅
  • 需要维持长连接,有断线重连机制

嗯,这里要注意。WebSocket虽然快,但有个坑——断线。我在项目中遇到过,某次行情剧烈波动,WebSocket连接突然断了,我这边完全没收到数据,策略还在傻傻地按旧数据交易。那一次亏了不少。

避坑指南: 我曾经因为WebSocket断线没处理好,导致策略在无行情数据的情况下持续报单。后来我加了三层保障:心跳检测、自动重连、数据断流熔断。记住,WebSocket一定要有完善的断线重连机制。

4.2 行情数据格式:Level1 / Level2 / Level3

行情数据分等级,就像游戏装备分白装、蓝装、紫装。等级越高,信息越详细,延迟越低,当然价格也越贵。

4.2.1 Level1 行情

Level1就是最基础的行情数据。你打开任何一个股票软件,看到的买一卖一、最新价、涨跌幅,就是Level1。

字段 说明
最新价 当前最后一笔成交价
涨跌幅 相对于昨收的百分比
买一价/量 最高买入价及对应数量
卖一价/量 最低卖出价及对应数量
成交量 累计成交数量

Level1够用吗?做普通交易够了。做高频?远远不够。你想想看,Level1只给你看最上面一档,下面还有九档的挂单你完全看不到,这就像打牌只看对方一张牌。

4.2.2 Level2 行情

Level2就厉害多了。它给你看十档行情,也就是买一到买十、卖一到卖十,全部摆在你面前。

Level2 核心数据:
  • 十档买卖盘口(买一~买十,卖一~卖十)
  • 逐笔成交明细(每一笔成交的时间、价格、数量)
  • 委托队列(买一和卖一的前50笔挂单明细)
  • 资金流向统计

有了Level2,你能看到什么?你能看到大单在偷偷吃货,能看到主力在挂假单诱多。我习惯用Level2的委托队列来判断主力意图。比如卖一挂了1000手大单,但委托队列里全是小单,那大概率是吓人的假单。

4.2.3 Level3 行情

Level3是最高等级,目前只有少数交易所提供,比如纳斯达克的TotalView。它给你看整个订单簿,所有挂单,每一档都清清楚楚。

Level3的数据量极大。一个热门股票,订单簿可能有几千档。每秒更新几百次。处理起来非常考验系统性能。

我的建议: 做A股高频,Level2基本够用。做美股高频,Level3是标配。但Level3的数据处理成本很高,你需要一个高性能的消息队列和内存数据库来支撑。

4.3 数据订阅与推送

搞清楚了数据格式,接下来就是怎么订阅和接收数据了。

4.3.1 订阅流程

订阅行情,一般分三步走:

  1. 建立连接:通过WebSocket连接到交易所的行情服务器
  2. 发送订阅请求:告诉交易所你要订阅哪些品种、哪些数据类型
  3. 接收推送:交易所开始往你这边推数据

代码示例,以币安的WebSocket订阅为例:

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    # 处理行情数据
    print(f"收到行情: {data}")

def on_error(ws, error):
    print(f"连接错误: {error}")

def on_close(ws, close_status_code, close_msg):
    print("连接关闭,准备重连...")

def on_open(ws):
    # 订阅BTC/USDT的深度行情
    subscribe_msg = {
        "method": "SUBSCRIBE",
        "params": ["btcusdt@depth20@100ms"],
        "id": 1
    }
    ws.send(json.dumps(subscribe_msg))

# 启动WebSocket
ws = websocket.WebSocketApp(
    "wss://stream.binance.com:9443/ws",
    on_open=on_open,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close
)
ws.run_forever()

4.3.2 推送频率控制

交易所不会无限制地推数据。一般有几种频率:

  • 实时推送:有变化就推,延迟最低
  • 固定间隔:比如每100ms推一次快照
  • 增量推送:只推送变化的部分,减少数据量

我个人习惯用增量推送+定时快照的组合。增量推送保证实时性,定时快照用来校验数据一致性。万一增量数据丢了,快照能帮你恢复。

注意: 有些交易所的WebSocket推送会有数据乱序的问题。比如先推送了第100笔成交,再推送第99笔。你处理的时候一定要按时间戳排序,否则策略会出错。

4.4 数据缓存策略

数据来了,不能直接扔给策略。你得先缓存起来,做预处理。为什么?因为行情数据太快了,策略处理不过来。你想想看,Level2数据每秒可能更新几百次,你的策略如果每次都要重新计算,CPU直接拉满。

4.4.1 缓存层级

我一般分三层缓存:

层级 存储介质 用途
L1 内存缓存 Python字典/Redis 最新行情快照,毫秒级访问
L2 本地缓存 SQLite/InfluxDB 分钟级历史数据,用于回测和分析
L3 持久化存储 ClickHouse/Parquet 全量历史数据,用于模型训练

4.4.2 缓存更新策略

缓存不是存进去就不管了。你得有更新策略:

  • 写时更新:每来一条数据,立即更新缓存。适合高频场景。
  • 定时更新:每100ms批量更新一次。适合对实时性要求不高的场景。
  • 懒更新:策略需要时才去拉最新数据。适合低频策略。

做高频,我推荐写时更新。虽然CPU开销大一点,但数据最新。不过要注意,写时更新要加锁,防止多线程同时写导致数据错乱。

一个小技巧: 我习惯用环形缓冲区来缓存行情数据。比如开一个长度为1000的数组,新数据来了就覆盖最旧的数据。这样既保证内存不暴涨,又能快速获取最近N笔数据。

4.4.3 数据去重与校验

交易所偶尔会推送重复数据。你如果不做去重,策略可能会重复下单。我一般用(交易对, 时间戳, 序号)作为唯一键来去重。

另外,数据校验也很重要。比如检查价格是否在合理范围内,成交量是否异常。我曾经遇到过交易所推送的价格突然变成0,如果没做校验,策略直接按0价格下单,后果不堪设想。

知识体系总览

下面这张图,把数据源接入的核心逻辑串起来了:

数据源接入核心架构 交易所行情服务器 接入方式 REST API (低频/查询) WebSocket (高频/订阅) 行情数据格式 Level1 (基础行情) Level2 (十档+逐笔) Level3 (全量订单簿) 数据缓存策略 L1 内存缓存 (毫秒级) L2 本地缓存 (分钟级) L3 持久化存储 (全量)

从交易所到你的策略,数据流就是上面这条链路。每一步都有坑,每一步都需要精心设计。

好了,数据源接入这块就讲到这里。记住一句话:数据是量化交易的血液,血液不干净,策略再强也是白搭。


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