Day 103
Day 103:订单簿与市场数据架构
L1/L2/L3数据层级、WebSocket实时推送、K线OHLCV计算与存储、TradingView集成、DeFi链上数据特殊性
2026-04-20
交易订单簿市场数据WebSocketK线TradingViewDay103
核心概念
什么是市场数据?
一句话定义:市场数据(Market Data)是交易所产生的所有与价格、订单、成交相关的信息流。它是交易者做决策的"眼睛",也是算法交易的"输入源"。
类比理解:如果交易所是一个菜市场,市场数据就是每个摊位上贴的"今日价格牌"+"排队人数"+"最近成交记录"。你看着这些信息决定去哪个摊位买菜。
市场数据的三个层级
市场数据层级体系:
═══════════════════════════════════════
L1 数据(Level 1)— 最佳报价
├── 内容:Best Bid (最高买价) + Best Ask (最低卖价)
├── 信息量:最少
├── 频率:高(每次变动推送)
├── 用途:基础交易决策、散户行情显示
├── 带宽:~1KB/s per symbol
└── 示例:
BTC/USDT: Bid $60,050 × 2.5 | Ask $60,055 × 1.8
L2 数据(Level 2)— 订单簿深度
├── 内容:多个价格档位的挂单量
├── 信息量:中等
├── 频率:中(通常推送前5-20档)
├── 用途:判断支撑/阻力、流动性分析
├── 带宽:~10KB/s per symbol
└── 示例:
Ask $60,100 × 0.5
Ask $60,080 × 1.2
Ask $60,055 × 1.8 ← Best Ask
─────────────────
Bid $60,050 × 2.5 ← Best Bid
Bid $60,030 × 3.1
Bid $60,000 × 8.4
L3 数据(Level 3)— 完整订单流
├── 内容:每一笔订单的新增/修改/取消
├── 信息量:最多
├── 频率:最高(每个订单事件)
├── 用途:HFT、做市策略、微观结构研究
├── 带宽:~100KB-1MB/s per symbol
└── 示例:
10:00:01.001 | ADD | Buy 2.5 BTC @ $60,050 | OrderID: #12345
10:00:01.002 | CANCEL | OrderID: #12340
10:00:01.003 | TRADE | 1.0 BTC @ $60,055 | Buyer:#12345 Seller:#12341
10:00:01.004 | MODIFY | OrderID: #12342 | NewQty: 3.0
知识点详解
知识点 1:订单簿深度可视化与分析
订单簿的完整视觉表示:
═══════════════════════════════════════
价格 ($) │ 卖出量 │ 累计卖出
─────────────┼──────────┼──────────
60,200 │ 0.3 │ 15.8
60,150 │ 1.5 │ 15.5
60,100 │ 2.0 │ 14.0
60,080 │ 3.2 │ 12.0 ← 卖方堆积(阻力)
60,055 │ 1.8 │ 8.8 ← Best Ask
════════════╪══════════╪══════════
60,050 │ 2.5 │ 2.5 ← Best Bid
60,030 │ 3.1 │ 5.6
60,000 │ 8.4 │ 14.0 ← 买方堆积(支撑)
59,950 │ 2.0 │ 16.0
59,900 │ 1.2 │ 17.2
Spread = $60,055 - $60,050 = $5 (0.008%)
Mid Price = ($60,055 + $60,050) / 2 = $60,052.5
深度图(Depth Chart):
累计量
20│
│ ╲ ╱
15│ ╲ 卖方 ╱
│ ╲ ╱
10│ ╲ ╱ 买方
│ ╲ ╱
5│ ╲ ╱
│ ╲╱
0│────────────────────────
59,800 60,000 60,200 价格
订单簿的关键指标:
├── Spread(买卖价差):流动性衡量
├── Depth(深度):大单承受能力
├── Imbalance(不平衡):方向指示
│ Imbalance = (BidQty - AskQty) / (BidQty + AskQty)
│ > 0:买方强 → 价格可能上涨
│ < 0:卖方强 → 价格可能下跌
└── Resilience(弹性):被吃后恢复速度
知识点 2:WebSocket 实时推送架构
市场数据推送架构设计:
═══════════════════════════════════════
┌─────────────┐
│ 撮合引擎 │ ← 产生原始市场数据
└──────┬──────┘
│ 内部消息总线(如 Kafka/Aeron)
↓
┌─────────────┐
│ 数据聚合器 │ ← 汇聚、去重、格式化
│ ├── L1聚合 │
│ ├── L2聚合 │
│ ├── K线计算 │
│ └── 指标计算 │
└──────┬──────┘
│
↓
┌─────────────────────────────────┐
│ WebSocket Gateway │ ← 面向用户
│ ┌──────────────────────────┐ │
│ │ Channel Manager │ │
│ │ ├── orderbook.BTC-USDT │ │
│ │ ├── trades.BTC-USDT │ │
│ │ ├── ticker.BTC-USDT │ │
│ │ └── kline.BTC-USDT.1m │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ Connection Manager │ │
│ │ ├── 10万+ 并发连接 │ │
│ │ ├── 心跳检测 │ │
│ │ └── 自动重连 │ │
│ └──────────────────────────┘ │
└──────────────┬──────────────────┘
│ WebSocket
↓
┌──────┐ ┌──────┐ ┌──────┐
│ 用户1 │ │ 用户2 │ │ 用户N │
└──────┘ └──────┘ └──────┘
关键设计决策:
├── 推送模式:增量更新 vs 全量快照
│ └── 增量:只推送变化(省带宽)
│ └── 全量:定期推送完整订单簿(防丢失)
│ └── 最佳实践:初始全量 + 后续增量
│
├── 频率控制:
│ └── L1:每次变化推送(高频)
│ └── L2:100ms聚合推送(平衡)
│ └── K线:每秒推送
│
├── 压缩:
│ └── 消息压缩(gzip/deflate)
│ └── 二进制协议(Protobuf/FlatBuffers)
│ └── 差量编码(只传变化的字段)
│
└── 扩展性:
└── 水平扩展WebSocket服务器
└── 按交易对分片
└── CDN加速(Cloudflare WS)
WebSocket 消息格式示例
// 订阅请求
{
"method": "SUBSCRIBE",
"params": ["orderbook.BTC-USDT.L2", "trades.BTC-USDT"],
"id": 1
}
// 订单簿快照(初始)
{
"channel": "orderbook.BTC-USDT.L2",
"type": "snapshot",
"data": {
"bids": [[60050, 2.5], [60030, 3.1], [60000, 8.4]],
"asks": [[60055, 1.8], [60080, 3.2], [60100, 2.0]],
"sequence": 1000001,
"timestamp": 1713600000000
}
}
// 订单簿增量更新
{
"channel": "orderbook.BTC-USDT.L2",
"type": "update",
"data": {
"bids": [[60050, 3.0]], // 更新60050档位的量
"asks": [[60055, 0]], // 量=0表示删除该档位
"sequence": 1000002
}
}
// 成交推送
{
"channel": "trades.BTC-USDT",
"data": {
"price": 60055,
"qty": 0.5,
"side": "buy", // 主动方
"timestamp": 1713600001234,
"trade_id": "T123456"
}
}
知识点 3:K线(OHLCV)计算与存储
K线数据结构:
═══════════════════════════════════════
OHLCV = Open, High, Low, Close, Volume
一根K线代表一个时间周期内的价格运动:
│ High ($60,200)
│ │
│ ┌──────┤
│ │ │ ← 实体(Open和Close之间)
│ │ Close│ = $60,150
│ │ │
│ ───┼──────┤ Open = $60,050
│ │ │
│ └──────┤
│ │
│ Low ($59,900)
Volume = 该周期内总成交量
常见K线周期:
├── 1m, 3m, 5m, 15m, 30m(分钟级)
├── 1h, 2h, 4h, 6h, 12h(小时级)
├── 1d, 3d, 1w(日/周级)
└── 1M(月级)
K线的实时计算
K线实时计算逻辑:
═══════════════════════════════════════
// 伪代码
class KlineBuilder {
currentCandle: {
open: number,
high: number,
low: number,
close: number,
volume: number,
startTime: number
}
function onTrade(price, quantity, timestamp):
// 检查是否需要开新K线
if timestamp >= currentCandle.startTime + INTERVAL:
// 保存当前K线
saveCandle(currentCandle)
// 开始新K线
currentCandle = {
open: price,
high: price,
low: price,
close: price,
volume: quantity,
startTime: getIntervalStart(timestamp)
}
else:
// 更新当前K线
currentCandle.high = max(currentCandle.high, price)
currentCandle.low = min(currentCandle.low, price)
currentCandle.close = price
currentCandle.volume += quantity
}
多周期K线的优化:
├── 从1分钟K线聚合生成更大周期
│ 5m = aggregate(5个1m)
│ 1h = aggregate(60个1m)
│ 1d = aggregate(1440个1m)
├── 避免重复计算
└── 内存中维护所有活跃周期的当前K线
K线存储方案
K线数据存储选型:
═══════════════════════════════════════
方案1:时序数据库(推荐)
├── InfluxDB
│ └── 写入快、压缩好、查询DSL强
├── TimescaleDB(PostgreSQL扩展)
│ └── SQL兼容、成熟稳定
├── QuestDB
│ └── 极致写入性能、SQL兼容
└── ClickHouse
└── OLAP强、压缩率极高
方案2:Redis + 持久化
├── 最近K线放Redis(热数据)
├── 历史K线存时序DB(冷数据)
└── 适合:超低延迟访问需求
存储量估算:
假设:1000个交易对 × 9个周期
1分钟K线:
├── 每根:48字节(OHLCV + timestamp)
├── 每天:1440根 × 48B = 69KB/交易对
├── 1000对/天:69MB
└── 1年:~25GB
全部周期:
└── 1年总量:~30GB(压缩后~5GB)
知识点 4:TradingView 集成
TradingView 集成架构:
═══════════════════════════════════════
TradingView 提供两种集成方式:
方式1:UDF(Universal Data Feed)
├── 最简单的HTTP API方式
├── TradingView请求你的API获取数据
├── 接口规范:
│ GET /config → 支持的功能
│ GET /symbol_info → 交易对信息
│ GET /symbols?symbol= → 单个交易对
│ GET /history?symbol=&from=&to=&resolution= → K线数据
│ GET /time → 服务器时间
└── 适合:简单集成、数据量不大
方式2:JS API(推荐)
├── 通过JavaScript接口提供数据
├── 支持实时推送(WebSocket)
├── 接口定义:
│ onReady(callback)
│ searchSymbols(input, symbolType, exchange, onResult)
│ resolveSymbol(symbolName, onResolve, onError)
│ getBars(symbolInfo, resolution, from, to, onResult, onError)
│ subscribeBars(symbolInfo, resolution, onTick, listenerGUID)
│ unsubscribeBars(listenerGUID)
└── 适合:专业交易平台、需要实时数据
集成架构图:
┌────────────────────────────────────┐
│ TradingView Widget │
│ ┌──────────────────────────┐ │
│ │ Charting Library │ │
│ │ ├── 图表渲染 │ │
│ │ ├── 技术指标 │ │
│ │ ├── 画图工具 │ │
│ │ └── 交易面板 │ │
│ └───────────┬──────────────┘ │
│ │ JS API / UDF │
│ ┌───────────┴──────────────┐ │
│ │ Datafeed Adapter │ │ ← 你需要实现的
│ │ ├── HTTP: 历史K线 │ │
│ │ └── WS: 实时更新 │ │
│ └──────────────────────────┘ │
└──────────────┬─────────────────────┘
│
[你的后端API]
├── K线历史数据
├── 实时价格推送
└── 交易对配置
知识点 5:DeFi 链上数据的特殊性
DeFi 市场数据 vs CeFi 市场数据:
═══════════════════════════════════════
差异1:数据来源
CeFi:交易所内部系统直接产生
DeFi:从区块链事件日志中提取
├── Swap事件 → 成交数据
├── Sync事件 → 价格更新
├── Mint/Burn事件 → 流动性变化
└── 需要自己索引和解析
差异2:更新频率
CeFi:毫秒级连续更新
DeFi:出块时间间隔(2s-12s)
├── Ethereum:12秒
├── Solana:400ms
├── Arbitrum:250ms
└── K线可能有"空洞"(该周期内无交易)
差异3:订单簿
CeFi:传统CLOB(中心化限价订单簿)
DeFi-AMM:无订单簿,流动性曲线即"虚拟订单簿"
├── Uniswap v2:x*y=k → 无限深度但滑点递增
├── Uniswap v3:集中流动性 → 类似订单簿
└── 从流动性分布计算"虚拟订单簿深度"
差异4:MEV影响
CeFi:成交顺序由撮合引擎决定(FIFO)
DeFi:成交顺序由区块构建者决定
├── 三明治攻击改变实际成交价
├── 前置运行(Frontrunning)
└── 影响K线的"真实性"
差异5:多池碎片化
CeFi:一个交易对一个订单簿
DeFi:同一交易对可能在多个池子
├── ETH/USDC 在 Uniswap v3 0.05%池
├── ETH/USDC 在 Uniswap v3 0.3%池
├── ETH/USDC 在 Curve
└── 需要聚合所有池子的数据
DeFi市场数据解析架构:
┌─────────────┐
│ 区块链节点 │
│ (RPC/WS) │
└──────┬──────┘
↓
┌─────────────┐
│ 事件索引器 │ ← The Graph / 自建索引
│ ├── Swap │
│ ├── Sync │
│ └── events │
└──────┬──────┘
↓
┌─────────────┐
│ 数据聚合 │ ← 多池聚合、价格计算
│ ├── 价格 │
│ ├── 成交量 │
│ └── 流动性 │
└──────┬──────┘
↓
┌─────────────┐
│ K线/行情API │ ← 标准化输出
└─────────────┘
面试题解析
如何设计支持百万用户的实时市场数据系统?
简短回答(30秒版本):
分层架构 + 扇出模式。撮合引擎产生原始数据→通过消息总线(Kafka)分发→数据聚合服务计算各种视图(L1/L2/K线)→WebSocket网关层水平扩展支撑百万连接→按交易对分片、按数据类型分频道、增量推送减少带宽。
详细回答(2分钟版本):
百万用户实时市场数据系统设计:
═══════════════════════════════════════
容量估算:
├── 100万用户同时在线
├── 平均每用户订阅5个频道
├── 每个频道每秒10条消息
├── 每条消息~200字节
├── 总推送:100万 × 5 × 10 × 200B = 10GB/s
└── 这个量级需要精心设计
架构分4层:
Layer 1:数据生产(撮合引擎 → 消息总线)
├── Kafka/Aeron 作为中间件
├── 按交易对分区(Partition)
├── 保证同一交易对的消息有序
└── 吞吐:100万消息/秒
Layer 2:数据处理(聚合 + 计算)
├── L1/L2/L3数据聚合
├── K线实时计算(多周期)
├── 指标计算(VWAP/MA等)
└── 可以水平扩展(按交易对分片)
Layer 3:WebSocket Gateway(面向用户)
├── 每台服务器支撑5-10万连接
├── 需要10-20台服务器
├── 负载均衡:按用户ID一致性哈希
├── 订阅管理:高效的频道→连接映射
└── 消息广播:同一频道的消息只处理一次
Layer 4:客户端优化
├── 增量更新(只推变化)
├── 客户端节流(前端限制刷新率)
├── 离屏标签页暂停推送
└── 自动降级(网络差时切换低频模式)
追问准备:
- Q:如何处理WebSocket断连重连?A:客户端记录最后收到的sequence number,重连后请求该sequence之后的增量+一次全量快照。服务端保持短时间的消息回放缓冲。
- Q:如何保证消息不丢失?A:在关键路径上使用sequence number保序;客户端检测到gap时主动请求重传或快照。对于行情数据,允许少量丢失(最终一致即可)。
产品经理视角
PM在市场数据产品中的关键决策:
═══════════════════════════════════════
1. 数据层级与收费策略
├── 免费:L1 + 基础K线
├── 专业版:L2深度 + 更多周期
├── 机构版:L3订单流 + 原始数据
└── 这是交易所的重要收入来源
2. 延迟与用户体验
├── 散户:100ms延迟可接受
├── 专业交易者:<10ms
├── HFT:<1ms(需要Co-location)
└── 不同用户群的需求截然不同
3. 图表功能优先级
├── P0:K线、深度图、成交列表
├── P1:技术指标(MA/MACD/RSI)
├── P2:画图工具、多图表布局
└── P3:策略回测、自定义指标
4. DeFi特殊考虑
├── 多池聚合价格 vs 单池价格
├── MEV影响的标注
├── 流动性深度展示
└── Gas成本在交易面板中的集成
明日预告
Day 104:CeFi-DeFi 桥接架构 — 混合交易系统设计 — 性能与去中心化的平衡术
- 三种混合模式详解
- 信任假设与活性风险分析
- 强制包含机制(L1逃生舱)
- KYC Hooks 合规层设计
- App-chain vs Rollup 的取舍