Ⅰ · 概念地基 — 1.2

消息总线与事件流

自研框架里出现频率最高的名词。它解决的是一个具体问题,而不是抽象的优雅。

它要解决什么

系统里有若干个“产生消息的东西”(行情接入、订单执行、策略自身)和若干个“想知道消息的东西”(策略、风控、持仓、日志、录制器)。如果让它们互相直接调用,连接数是乘法关系——加一个消费者,要改所有生产者。

**消息总线把它变成加法:生产者只管往一个主题(topic)**上发布,消费者只管订阅自己关心的主题。两边都不知道对方存在。

直连:加一个消费者要改所有生产者行情接入订单执行策略风控持仓日志3 × 3 = 9 条连接消息总线:双方都只与总线通信行情接入订单执行策略MessageBus风控持仓日志3 + 3 = 6 条连接发布方不知道谁在听两种通信模式发布/订阅 —— 一条报价广播给所有订阅者,发布方不等待、不关心有几个接收方请求/响应 —— 策略请求一段历史数据,总线负责把响应correlate回请求方录制、回放、审计都挂在总线上:所有消息都流经同一处,加一个订阅者就能全量留痕。
解耦的实际含义是"发布方不需要知道接收方"。因此加一个消费者——录制器、审计、外部推送——不需要改动任何现有组件,只需多一个订阅。

对照:OnTick() 是什么

OnTick() 是终端对 EA 的直接回调:一个生产者、一个消费者、硬连接。这既是它简单的原因,也是它的边界——你无法让第二个东西“也听一下这条报价”,除非把逻辑写进同一个 OnTick() 里。

这解释了一个常见现象:MT5 里想加日志、加录制、加外部推送,最后都只能塞进 EA 内部,因为没有一个“消息经过的地方”可以挂上去。

一条报价的完整路径

把抽象落到具体。以 Nautilus 为例,一条 QuoteTick 从网卡到策略:

  1. adapter 收到网络字节,按交易所协议解析
  2. 转成框架内的数据对象 QuoteTick,带纳秒时间戳、买价、卖价、买量、卖量
  3. 发布到总线的对应主题(按品种与数据类型划分)
  4. DataEngine 分发给该主题的所有订阅者
  5. 写入 Cache——后续任何组件想读“最新报价”或“最近 N 条”,从缓存读,不必自己保存
  6. 触发策略的 on_quote_tick(),同时触发所有注册在该品种上的指标

第 5 步是 MT5 里没有对应物的一环:缓存是一个共享组件,不是每个 EA 各自维护的数组。风控、持仓、策略读到的是同一份数据。