3、事件驱动型套利系统整体设计

好,咱们直接进入正题。这一章我打算把整个系统的骨架给你拆开来看。说白了,事件驱动型套利系统,核心就四个字:闻风而动。市场上一有风吹草动,系统就得立刻反应,抢在别人前面把利润吃掉。

我个人习惯把这类系统比作一个「特种作战小队」。每个模块各司其职,协同作战。你想想看,如果情报部门(事件源)发现了目标,但指挥中心(事件处理器)反应慢了半拍,或者突击队(执行模块)的装备出了问题,那整个行动就泡汤了。

核心要点: 事件驱动 ≠ 高频交易。它更关注「逻辑触发」,而不是「纳秒级速度」。很多套利机会其实存在于分钟级别甚至小时级别。

3.1 系统架构总览

先给你看一张我画的架构图。这张图我迭代了不下二十个版本,从最初的单体应用,到后来的微服务,再到现在的「事件流 + 状态机」混合架构。嗯,这里面的坑我踩过不少。

事件驱动型套利系统架构图 事件源层 (Event Sources) 行情数据源 新闻/公告源 社交媒体源 链上数据源 事件处理器层 (Event Processor) 数据清洗 & 标准化 事件分类 & 优先级 事件去重 & 聚合 事件缓存 策略引擎层 (Strategy Engine) 信号生成器 风险过滤器 组合优化器 回测校验器 执行模块层 (Execution Module) 订单路由 滑点控制 成交反馈 风控熔断

这张图你看懂了吗?从上到下,数据流是单向的,但反馈回路是双向的。比如执行模块成交后,会生成一个「成交事件」重新进入事件处理器,形成闭环。我在第一版设计时忽略了这一点,结果订单状态经常不同步,吃了不少亏。

3.2 核心模块划分

咱们把每个模块拆开细聊。这四个模块,缺一个都不行。

3.2.1 事件源

事件源就是系统的「眼睛」和「耳朵」。它负责从各种渠道捕获原始数据。我把它分为两类:

  • 结构化事件源:交易所的行情 tick、订单簿快照、K线数据。这些数据格式固定,解析起来相对简单。
  • 非结构化事件源:新闻标题、推特消息、财报PDF。这些需要 NLP 处理,难度大,但信息价值往往更高。
我的经验: 千万别小看数据源的质量。我曾经接入过一个免费行情源,结果它的 tick 数据经常丢包,导致我的套利信号频繁误报。后来换了付费数据源,误报率直接降了 70%。一分钱一分货,在金融数据上尤其如此。

3.2.2 事件处理器

事件处理器是系统的「大脑皮层」。原始数据进来后,它要做三件事:

  1. 清洗:去掉乱码、空值、异常值。比如某交易所突然返回一个价格为 0 的 tick,这种数据必须过滤掉。
  2. 标准化:把不同数据源的数据统一成内部格式。比如 A 交易所的「BTC/USDT」和 B 交易所的「btc-usdt」,要映射成同一个交易对 ID。
  3. 优先级排序:给每个事件打上时间戳和优先级标签。比如「交易所宕机公告」的优先级远高于「某个地址转账 0.1 ETH」。

这里有个细节:事件处理器必须支持乱序到达。网络延迟会导致事件 B 先到、事件 A 后到。如果处理器不做乱序处理,策略引擎就会基于错误的事件顺序做出判断。我曾经因为这个 bug,在回测里看起来完美的策略,实盘一跑就亏钱。

3.2.3 策略引擎

策略引擎是整个系统的「心脏」。它接收处理好的事件,然后决定「要不要交易」以及「怎么交易」。

我个人习惯把策略引擎设计成插件化的。每个策略都是一个独立的插件,可以热插拔。这样好处很明显:

  • 新策略上线不需要重启整个系统
  • 某个策略出 bug 了,可以单独禁用,不影响其他策略
  • 回测时可以直接复用生产环境的策略代码

策略引擎内部通常包含三个子模块:

子模块 职责 常见坑点
信号生成器 根据事件计算买卖信号 信号延迟:计算耗时超过事件间隔
风险过滤器 检查当前仓位、资金是否允许交易 忘记考虑「最小交易量」限制
组合优化器 多策略同时触发时,决定执行优先级 策略之间互相抢资金,导致滑点放大

3.2.4 执行模块

执行模块是系统的「手脚」。它负责把策略引擎的决策变成真实的订单,发送到交易所。

这里我要强调一点:执行模块不是简单的订单转发器。它需要处理:

  • 订单拆分:大单拆成小单,减少市场冲击
  • 路由选择:多个交易所都有流动性时,选最优的那个
  • 重试机制:订单失败后自动重试,但要防止死循环
  • 风控熔断:当亏损超过阈值时,自动停止所有交易
避坑指南: 我曾经在实盘中遇到过执行模块的「幽灵订单」问题。原因是重试机制没加幂等性检查,同一个订单被重复发送了三次,结果仓位直接翻了三倍。后来我强制要求每个订单必须携带全局唯一的 requestId,交易所根据这个 id 做去重。

3.3 数据流设计

数据流设计决定了系统的吞吐量延迟。我见过太多系统,模块划分得挺好,但数据流设计得一塌糊涂,结果性能惨不忍睹。

我的设计原则是:事件驱动 + 异步非阻塞。每个模块之间通过消息队列解耦,而不是直接调用 API。

具体的数据流路径如下:

  1. 采集阶段:事件源模块把原始数据推送到 Kafka/RabbitMQ 的「原始事件队列」
  2. 处理阶段:事件处理器从队列中消费数据,处理后推送到「标准化事件队列」
  3. 决策阶段:策略引擎订阅标准化事件,运行策略逻辑,把交易信号推送到「信号队列」
  4. 执行阶段:执行模块从信号队列中获取指令,发送订单到交易所,并把成交结果推送到「成交反馈队列」
  5. 闭环阶段:成交反馈事件重新进入事件处理器,更新系统的内部状态

为什么要用消息队列?说白了就三个原因:

  • 削峰填谷:行情爆发时,事件量可能是平时的 100 倍。消息队列能缓冲这些流量,防止系统被冲垮。
  • 模块解耦:事件处理器挂了,策略引擎还能继续处理已缓存的事件。不会因为一个模块的故障导致全系统瘫痪。
  • 便于扩展:某个模块处理能力不够了,直接加消费者实例就行。我见过一个系统,从单机版扩展到 10 个节点,代码一行没改,只改了配置文件。
数据流设计的黄金法则: 每个模块只依赖「事件」和「状态」,不依赖「其他模块」。模块之间只通过消息队列通信,不共享内存,不直接调用接口。

嗯,这一章的内容差不多就这些。系统架构图、核心模块、数据流设计,这三块是套利系统的地基。地基打不牢,后面盖再高的楼也是危房。下一章咱们会深入事件处理器的内部实现,聊聊怎么处理乱序事件和重复事件——这两个问题在实盘中特别常见,处理不好很容易出事故。


公众号:蓝海数据掘金营,微信deep3321