速查

交易系统架构:地图、时间、怎么切

一条行情从网卡进来,到变成一笔报单、拿回成交,经过哪些线程和数据结构;每一段大概多久;工业级系统怎么分线程、在哪里放队列、哪里必须汇合。

本页目录

这页是随时翻的架构总图,和硬件地图是一对:那页讲一次访存经过哪些硬件,这页讲一条行情经过哪些线程。三部分按“在哪 → 多快 → 怎么切”排:

  1. 架构地图:一条行情变成一笔成交,经过哪些框,每个框在哪篇文章里讲过。
  2. 时间:每一段大概多久,用来判断该抠哪一段。都是量级,不对应哪套系统。
  3. 怎么切:工业级系统怎么分线程、在哪里放队列、哪里必须汇合。

后面讲到新的组件,都补在这一页。

1. 架构地图:一条行情经过哪些框

交易系统架构地图:交易所的行情经网卡进行情接入线程,先写一份进广播环给落盘和监控,再按股票分到各分片线程算盘口和因子,汇合后进决策线程的策略、风控、订单管理和下单网关,经券商柜台到交易所;回报沿红线回到订单管理和账户状态,再回到策略。最下面一排是旁路:离线回放、落盘、监控、日志、交易员指令和客户端推送。

绿框是热线程,各绑一个隔离核;蓝框是线程之间交接数据的结构;黄框是汇合点;最下面一排虚线框是旁路。黑线是行情到报单,红线是回报回来,灰色虚线是旁路。点图打开可交互的版本:点一个框看它做什么、为什么这样设计;选一条路径,一步一步看怎么走。

1.1 一条行情的路:行情 → 报单

  1. 交易所把行情按通道组播出来,网卡收到包。
  2. 行情接入线程解码,按通道查序号,断档了就去补。
  3. 它先把这条写进广播环,给落盘、监控这些不赶时间的读者,写完就走(#5)。
  4. 再按股票查表,写进负责这只股票的分片队列(#4)。同一只股票永远进同一片,顺序不会乱。
  5. 分片线程更新这只股票的盘口,再增量更新它的因子,只碰自己那些股票的状态。
  6. 按 bar 截面的策略,要等所有分片算完这根 bar,再一起排名、打分。
  7. 决策线程里,策略定买什么、卖什么、撤哪些,过事前风控,交给订单管理;大单拆成小单按时间发。
  8. 下单网关编码成协议发出去,经券商柜台到交易所。

1.2 一笔回报的路:成交 → 持仓 → 策略

  1. 交易所撮合以后发出回报:确认、成交、撤单都算。回报经柜台回到下单网关。
  2. 订单管理更新这笔单子的状态:部分成交、全部成交、已撤。
  3. 账户状态跟着变:持仓、资金、在途单。它只让决策线程一个人改。
  4. 下一次决策用的就是新的持仓和资金,回路闭合;推送把变化送到交易员的屏幕上。

1.3 两种节奏:按 bar 截面,还是逐笔触发

按 bar 截面(多因子选股):所有股票算完一根 bar 再一起打分,节奏是毫秒到秒。几十纳秒的交接看不出来,要盯的是一波数据进来以后,多久全部算完。

逐笔触发(做市、套利):一条行情就可能触发一笔报单。分片线程里一路算信号、过风控、发单,不过汇合、不过队列,全程在微秒级;每多一次跨核交接,就多几十纳秒和一份抖动。

1.4 旁路:慢了也不能拖住热路径

落盘、日志、监控、客户端推送都在旁路上:热线程只往环或队列里写一条就走,慢活交给别的线程或进程。旁路可以隔一会读一批,但要算好容量,别被套圈。交易员的指令(启停、撤单、改参数)方向相反,经服务进程走队列送进决策线程。

1.5 文章索引:每个框在哪篇讲过

框文章
热线程绑核、网卡中断别打到热核#1 CPU 隔离与 NUMA
跨核交接为什么要几十纳秒、伪共享#2 内存序与缓存行 · MESI 专题 · 速查 2.6
热路径不现分配内存(订单、消息对象)#3 内存池与分配器
分片队列(SPSC)、异步日志#4 无锁队列与 Micro-Batching
广播环、落盘读者被套圈、重启续读、什么时候不用广播环#5 SPMC 广播环
几个线程的订单汇到一个网关(多生产者队列)、旁路的阻塞队列、队列满了阻塞还是丢#18 线程安全队列
分片线程里一条行情的计算缓存案例:一次行情重算 64 个期权
开盘前预热、账户快照的双缓冲#14 预取、预热与双缓冲
汇合、策略、风控、订单管理、下单网关本页第 3 部分;以后的文章再补

2. 时间:每一段大概多久

这里的数都是帮助理解的量级,不对应哪套系统。标了“本站测过”的出自对应文章,其余是常见量级。

一段大约多久说明
交易所 → 本机网卡托管机房直连:几 µs;经行情供应商转发:几 ms看离交易所多远、中间转几手
网卡 → 程序拿到包绕过内核:约 1 µs;走内核协议栈:约 5–10 µs内核协议栈要过中断、拷贝、系统调用
解码一条行情约 0.1–1 µs看格式
跨核交接一次(队列、环)约 20–100 ns一条缓存行从一个核搬到另一个核(速查 2.1)
往广播环写一条约 3–40 ns本站测过:全速写时,守在最新槽位上的读者越多越贵(#5 1.6)
一条行情更新盘口和因子几百 ns 到几 µs看因子多少;缓存命中差一层,就差好几倍
汇合等最慢的那个分片截面策略的节奏是 bar:毫秒到秒
策略和风控逐笔判断:微秒级;几千只股票一起排名:约百 µs都在内存里算
报单到交易所、拿回回报托管直连:往返几十到几百 µs;经普通券商柜台:到毫秒网络和撮合,自己能优化的不多
推送到交易员屏幕几十 ms给人看的,够用就行
写盘毫秒级所以只能放在旁路上

这张表说明两件事。一是几十纳秒的交接,放进毫秒级的链路里看不出来,只有整条链只有几微秒的系统才值得抠它。二是行情蜂拥的时候,慢的不是交接,是排队(3.5)。

3. 怎么切:工业级系统的几条规则

3.1 关键路径一个线程跑到底,队列只放旁路

最快的系统把收包、解码、更新盘口、判断、发单放在同一个线程里,一个函数接一个函数调下去,数据一直留在这个核的缓存里。每过一次线程之间的队列,就是一次跨核搬运,几十纳秒,外加一份抖动;所以队列只用来把落盘、日志、监控这些慢活甩出去。再快一档,是把解码甚至发单放进网卡上的 FPGA,软件只设参数。

3.2 多核靠按股票分片,片内跑到底

一个线程算不过来全市场,就按股票分给几个线程。同一只股票的消息必须按顺序处理,所以一只股票永远交给同一个线程;不同股票的线程并行算。要按消息量分,不按股票个数分:热门股的消息多得多,一般按前几天的消息量排一张“股票 → 线程”的表,开盘前定好。交易所的行情本来就按通道发,能直接按通道收,就不用自己再分一次;源头是一路全量的(比如供应商转发),才由接入线程按表分发。

3.3 一份数据给几个读者:广播环还是分开的 SPSC

读者都要全量、会增会减、在别的进程里(落盘、转发、监控),用广播环:写一份,生产者不看读者。读者各要一部分,或者丢了数据必须当场知道,用按读者分开的 SPSC。两者常叠着用:接入线程写一份进广播环,同时按股票写进各分片的队列(#5 1.7)。

3.4 汇合只放在必须汇合的地方,状态只让一个线程写

截面打分要等所有股票,所以有汇合点;单只股票自己的因子不用等别人。持仓、资金、在途单只让决策线程一个人改;推送、监控要看,就读它发布出来的快照,不碰正在改的那份。快照怎么发才不会读到一半新一半旧,见 #14 的双缓冲。

3.5 行情蜂拥:慢在排队,不在交接

开盘、收盘、消息面的时候,消息量是平时的好几倍。只要每条要做的活比消息到来的间隔长,就开始积压:后来的消息要等前面的算完,因子用的就是旧行情。过一次队列几十纳秒,跟排队比可以忽略;要省的是排队:按股票分给更多线程,或者让每条的活变少。

积压都堆在环和队列里:积压 =(到达速度 − 处理速度)× 持续时间,不能超过容量,广播环超过就会套圈(#5 2.3.2)。要知道够不够,拿真实行情把开盘那几分钟按原速放一遍,量每条从进来到算完的时间,再看积压怎么涨落。

3.6 旁路不许拖住热路径

热线程打日志、交给落盘,只往队列或环里写一条就走(#4);格式化、写盘、推送都在别的线程。不赶时间的读者不要死循环盯着最新槽位,隔一会读一批,省得每条都把生产者要写的那一行抢走一次(#5 1.6.5)。

3.7 回放用同一份代码

落盘的行情按原顺序喂回同一份因子代码,离线结果和盘中逐值对得上,才说明回放可信。改了代码也靠它做回归:同一天的行情,改前改后输出一个值都不变,才算没改坏。