1. 行情数据基础:理解Level-1与Level-2行情、快照与增量数据的概念、为什么需要合并

做量化交易这些年,我见过不少新手一上来就扎进策略堆里,结果连行情数据都没搞明白。说实话,行情数据是整个交易系统的地基。地基不稳,上面盖多高的楼都得塌。

今天咱们就聊聊行情数据最核心的几个概念。嗯,这些东西我当年也是踩了不少坑才真正吃透的。

1.1 Level-1 与 Level-2 行情:到底差在哪?

先问个问题:你在券商APP上看到的行情,和量化机构用的行情,是一回事吗?

答案很明确——不是。

国内交易所把行情数据分成了两个等级:Level-1 和 Level-2。说白了,一个是大路货,一个是VIP专享。

对比项 Level-1 行情 Level-2 行情
推送频率 约3秒一次 约0.5秒一次,甚至逐笔推送
盘口深度 5档买卖盘 10档买卖盘
逐笔成交 有(逐笔成交明细)
费用 免费 付费(每年数万到数十万)
适用场景 普通看盘、长线 高频交易、做市、量化

我个人习惯用Level-2做日内策略。为什么?因为3秒一次的Level-1,对于高频场景来说,信息滞后太严重了。你想想看,3秒在A股市场里,足够价格跳好几个档位了。

核心区别一句话总结:Level-1告诉你「刚才发生了什么」,Level-2告诉你「正在发生什么」。

1.2 快照与增量数据:两个完全不同的东西

好,接下来聊快照和增量。这两个概念,我建议你一定要分清楚。

快照(Snapshot),就是某一时刻的完整行情状态。比如10:00:00这一秒,某只股票的买一价、卖一价、成交量、持仓量……所有数据打包给你。

增量(Incremental/Delta),则是两次快照之间的变化。比如从10:00:00到10:00:03,买一价从10.01变成了10.02,成交量增加了100手。增量只告诉你「变了什么」,不告诉你「全部是什么」。

举个例子你就明白了:

// 快照数据(10:00:00)
{
  "symbol": "600519",
  "time": "10:00:00.000",
  "last_price": 1880.00,
  "volume": 1250000,
  "bid1_price": 1879.50,
  "bid1_volume": 200,
  "ask1_price": 1880.50,
  "ask1_volume": 150
}

// 增量数据(10:00:03)
{
  "symbol": "600519",
  "time": "10:00:03.000",
  "last_price": 1881.00,    // 变了
  "volume": 1250100,        // 增加了100手
  "bid1_price": 1880.00,    // 变了
  "bid1_volume": 180,       // 变了
  "ask1_price": 1881.00,    // 变了
  "ask1_volume": 120        // 变了
}

看到没?增量数据只包含了变化的部分。如果某个字段没变,增量里可能压根就不出现。

小技巧:我在项目中遇到过,有些交易所的增量数据格式很「偷懒」——只推送变化字段,没变化的直接省略。所以解析时一定要做空值判断,别想当然地认为每个字段都有值。

1.3 为什么需要合并?不合并行不行?

你可能会问:既然快照已经包含了完整数据,我直接用快照不就行了?干嘛还要合并增量?

嗯,这个问题问得好。原因有三:

  1. 带宽和性能:快照数据量大,如果每秒推送一次快照,网络带宽和CPU开销都扛不住。增量数据小得多,传输效率高。
  2. 实时性要求:快照通常几秒才推送一次,但增量可以毫秒级推送。如果你只靠快照,中间这几秒的行情变化你就错过了。
  3. 历史回放:做回测时,你需要精确到每一笔的行情变化。只存快照会丢失中间细节,存增量+定期快照才是标准做法。

说白了,快照是「锚点」,增量是「变化流」。两者结合,才能既保证数据完整,又保证实时高效。

注意:合并增量时,一定要处理好时序问题。我曾经踩过一个坑——增量数据到达的顺序和实际发生顺序不一致,导致合并出来的快照数据错乱。后来我加了个序列号校验,才彻底解决。

1.4 合并的核心逻辑:一张图看懂

下面这张图,是我自己总结的合并流程。你看完应该就明白了。

行情快照与增量数据合并流程 快照数据 (完整状态,低频推送) 增量数据 (变化部分,高频推送) 合并引擎 (快照 + 增量 → 最新状态) 内存中的最新快照 (持续更新,保持最新状态) 输出给策略/回测引擎 (完整的、实时的行情数据) 图1:快照作为基础状态,增量数据不断叠加更新,形成实时行情流

流程其实很简单:

  • 先拿一个快照作为「基准状态」
  • 然后不断接收增量数据
  • 把增量「叠」到快照上,更新对应字段
  • 最终得到的就是最新的完整行情

我在实际项目中,通常每5分钟或者每收到1000条增量后,重新拉一次快照。为什么?因为增量数据在传输过程中可能有丢包,时间久了误差会累积。定期用快照「校准」一下,能保证数据一致性。

一句话记住:快照是「底图」,增量是「更新层」。底图定期换,更新层实时刷,两者结合就是完整的行情视图。

1.5 实际开发中的几个坑

最后分享几个我踩过的坑,希望能帮你少走弯路:

  • 增量乱序:网络延迟可能导致后发的增量先到。解决方案是给每条增量加一个单调递增的序列号,合并前先排序。
  • 快照与增量重叠:有时候快照到达时,增量已经处理了一部分。这时候要小心,别把增量覆盖了。我一般会记录快照的生成时间,只合并该时间之后的增量。
  • 内存管理:如果你同时监控几千只股票,每只股票都维护一个快照对象,内存开销不小。建议用对象池或者直接复用数据结构,减少GC压力。

好了,这一章的内容就到这里。行情数据的基础概念,说白了就是这些。下一章咱们会深入代码层面,看看怎么用Java/C++实现一个高效的合并引擎。


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