4、事件源设计(一):市场行情事件源(Tick、K线、订单簿)、事件标准化与封装

各位同学,今天我们来聊聊事件驱动型套利系统的第一个核心环节——事件源设计。说白了,就是你的系统怎么感知市场的变化。

我个人习惯把事件源比作系统的「眼睛」。眼睛要是瞎了,后面策略再牛也没用。我在早期做高频套利时,就吃过行情源不稳定的亏,那感觉,就像在高速公路上蒙着眼开车。

4.1 市场行情事件源的三大基石

市场行情数据,通常分为三个层次。你想想看,一个交易员盯盘,他看什么?无非就是这三样:

  • Tick 数据:最原始的交易记录。每一笔成交,就是一个 Tick。
  • K 线数据:对 Tick 数据的聚合。按时间窗口(1分钟、5分钟)统计出开盘、收盘、最高、最低。
  • 订单簿数据:市场深度。显示当前所有挂单的买价、卖价和数量。

嗯,这里要注意:不是所有套利策略都需要这三样。比如做跨期套利,你可能更关注 Tick 和订单簿;做统计套利,K 线就够了。

核心观点:事件源设计的本质,是把「市场发生了什么」翻译成「系统能理解的事件」。

4.2 Tick 事件源:最原始的市场脉搏

Tick 数据长什么样?我直接给你看一个典型的结构:

{
  "symbol": "BTCUSDT",
  "exchange": "binance",
  "timestamp": 1696000000123,
  "price": 27500.50,
  "volume": 0.15,
  "side": "buy",       // 主动买还是主动卖
  "trade_id": "123456789"
}

为什么 Tick 重要?因为它最真实。K 线可以造假(比如对敲拉收盘价),但 Tick 是逐笔成交,很难完全伪造。

我在项目中遇到过一个问题:Tick 数据量太大。一个主流交易所,每秒可能产生几千笔 Tick。如果每个 Tick 都触发一次策略计算,CPU 直接拉满。怎么办?

我的经验:不要直接消费原始 Tick。加一个「节流阀」——比如每 10 毫秒聚合一次,或者只处理价格变化超过 0.1% 的 Tick。

4.3 K 线事件源:聚合后的市场视图

K 线是 Tick 的「压缩包」。它牺牲了细节,换来了可读性。一个典型的 K 线事件:

{
  "symbol": "ETHUSDT",
  "interval": "1m",
  "open_time": 1696000000000,
  "close_time": 1696000059999,
  "open": 1850.00,
  "high": 1855.50,
  "low": 1848.20,
  "close": 1853.10,
  "volume": 1250.0
}

做 K 线事件源时,有个坑:K 线是「闭合」后才推送的。也就是说,当你知道这根 K 线收盘价时,它已经结束了。对于套利来说,这往往太晚了。

避坑指南:我曾经因为依赖 K 线收盘事件做开仓判断,结果滑点大得离谱。后来改成用「实时 K 线」——也就是当前正在形成的 K 线,每来一个 Tick 就更新一次。

4.4 订单簿事件源:市场的「暗流」

订单簿是套利系统的重头戏。它告诉你:谁在什么价位等着成交

一个典型的订单簿快照:

{
  "symbol": "BTCUSDT",
  "bids": [
    [27500.00, 1.5],   // 买价, 数量
    [27499.50, 2.3],
    ...
  ],
  "asks": [
    [27501.00, 0.8],
    [27501.50, 1.2],
    ...
  ],
  "timestamp": 1696000000123
}

订单簿事件有两种模式:

  • 全量快照:每隔一段时间(比如 100ms)推送整个订单簿。简单,但数据量大。
  • 增量更新:只推送变化的部分。比如「买一价变了」、「卖二数量减少了」。效率高,但实现复杂。

我个人建议:用增量更新 + 本地维护全量订单簿。这样既省带宽,又能随时拿到完整深度。

关键点:订单簿的「深度」比「价格」更重要。套利机会往往藏在挂单量的突变中。

4.5 事件标准化与封装:让数据「说同一种语言」

好了,现在你有了 Tick、K 线、订单簿。但问题来了——不同交易所的数据格式不一样

币安的 Tick 字段叫 "price",OKX 的可能叫 "px"。你总不能为每个交易所写一套策略吧?

所以我们需要一个「事件标准化层」。它的作用很简单:把各种原始数据,翻译成统一的内部事件

我设计的一个标准事件接口:

// 统一市场事件接口
interface MarketEvent {
  event_type: 'tick' | 'kline' | 'orderbook';
  symbol: string;
  exchange: string;
  timestamp: number;       // 统一用毫秒时间戳
  data: any;               // 具体数据
  raw: any;                // 保留原始数据,方便调试
}

为什么要保留 raw?因为有时候标准化会丢失信息。比如某个交易所的 Tick 里带了一个「交易者 ID」,标准化时你可能没考虑这个字段。保留 raw,方便后续回溯。

小技巧:在标准化层加一个「校验器」。检查时间戳是否合理、价格是否为正数、数量是否异常。我曾经因为交易所推送了一个价格为 0 的 Tick,差点导致策略爆仓。

4.6 整体架构图

下面这张图,展示了事件源在整个系统中的位置。你可以看到,原始数据经过「适配器」变成标准事件,然后进入事件总线,供策略消费。

事件驱动型套利系统 - 事件源架构 交易所 A Tick / K线 / 订单簿 交易所 B Tick / K线 / 订单簿 交易所 C Tick / K线 / 订单簿 事件标准化与封装层 统一接口:MarketEvent { event_type, symbol, exchange, timestamp, data, raw } 事件总线(Event Bus) 策略模块(消费事件) 原始数据 标准化 分发

4.7 总结与个人心得

事件源设计,说白了就是三个字:收、转、发

  • :从不同交易所接收原始数据。
  • :转换成统一的标准事件。
  • :推送到事件总线,供下游消费。

我在做这个系统时,最深的体会是:不要过度设计。一开始你只需要支持 Tick 和订单簿就够了。K 线可以后面再加。先跑起来,再优化。

最后一句:事件源是系统的「地基」。地基不稳,楼盖得再高也是危房。花时间把这块做扎实,后面你会感谢自己的。


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