交易技术

选择交易技术时要检查什么

延迟分层:秒级 > 1 秒;毫秒级 1—100 毫秒;微秒级 < 100 微秒…

选择交易技术时要检查什么
延迟分层:秒级 > 1 秒;毫秒级 1—100 毫秒;微秒级 < 100 微秒

这三组数字先决定技术路线。不是先选语言。不是先买服务器。也不是先接最快的柜台。

基本面低频策略,通常只需要秒级响应。日内趋势跟踪和均值回归,主要落在毫秒级。高频做市、统计套利、订单流交易,才进入微秒级约束。策略频次与系统延迟不匹配,结果只有两种:过度建设,或者性能不足。

选择交易技术时,要检查的不是某个软件的宣传参数。要检查整条链路。

行情从哪里进入。时间戳是否可信。策略何时计算。风险检查是否在热路径内。订单如何穿过柜台。回测和实盘是否使用同一套核心逻辑。每一个环节都可能改变最终结果。

先按策略频次定义延迟预算

延迟不是一个数字。它是一条分布。

平均延迟只能描述中心位置。交易系统更需要观察最大值、分位数和长尾。一次偶发的延迟尖峰,可能发生在订单有效期内。对高频策略而言,这比均值更重要。

策略本身决定了可接受的延迟范围。

策略类型常见延迟约束主要技术要求不应优先追求的指标
基本面驱动低频策略大于 1 秒稳定的数据处理、可靠的任务调度、可审计的数据库微秒级柜台穿透
日内趋势跟踪1—100 毫秒稳定行情订阅、低抖动计算、快速订单接口FPGA 硬件加速本身
日内均值回归1—100 毫秒Tick 数据连续性、特征计算效率、滑点建模单纯压低平均延迟
统计套利通常要求毫秒级,部分链路进入微秒级低延迟行情、订单簿建模、热路径优化研究代码的开发便利
高频做市与订单流交易小于 100 微秒低延迟网络、无垃圾回收热路径、极速柜台、硬件或内核级优化全链路使用同一种语言

这张表的重点不是分类。是预算。

假设策略信号的有效期为数百毫秒,那么把柜台穿透从几十微秒压到个位数微秒,未必改变策略结果。相反,如果策略依赖盘口微小变化,数据进入延迟、订单生成延迟和风控拦截延迟都可能直接影响成交概率。

因此,第一步应当把总延迟拆开:

1. 行情从交易所或数据源到策略进程的传输时间。

2. 数据解码、校验和标准化时间。

3. 因子或信号计算时间。

4. 订单生成时间。

5. 事前风控检查时间。

6. 订单发送到柜台的时间。

7. 柜台穿透到交易所的时间。

8. 回报从柜台返回策略系统的时间。

总延迟不是这些环节的宣传值简单相加。系统会出现排队、线程切换、缓存未命中、网络抖动和垃圾回收。需要测量端到端分布。

平均值不够

交易系统测试至少应记录以下结果:

  • 平均延迟。用于观察整体水平。
  • 中位数延迟。用于观察典型路径。
  • 九十百分位、九十九百分位延迟。用于观察多数异常情况。
  • 最大延迟。用于定位极端尖峰。
  • 延迟抖动。用于观察运行稳定性。
  • 各阶段延迟。用于定位瓶颈。

如果供应商只给出一个最低延迟,例如几微秒,而没有说明测试条件,数据价值有限。最低值通常对应空载、短链路、预热完成和特定消息类型。真实系统需要关注持续订阅和订单流量下的分布。

低延迟不是采购参数。它是按策略频次拆开的延迟预算。

对秒级策略,系统应优先保证数据完整性、任务稳定性和故障恢复。对毫秒级策略,重点转向低抖动和队列控制。对微秒级策略,线程调度、内存分配、网络路径和柜台实现都会进入核心设计。

不能用高频系统的标准评价低频策略。也不能用低频系统的便利性覆盖高频交易的热路径。

行情数据接入,先核验时间戳

策略计算再快,如果行情时间戳不可信,回测和实盘都会产生偏差。

行情接入质量主要看三件事:时间戳精度,交易所时钟一致性,Tick 连续性。还要观察长连接订阅下的端到端延迟分布。

时间戳必须区分来源

一条行情至少可能存在多个时间点:

  • 交易所生成时间。
  • 行情源服务器接收时间。
  • 网络网关转发时间。
  • 本地程序接收时间。
  • 策略进程开始处理的时间。
  • 策略进程完成处理的时间。

这些时间不能混为一个字段。

如果只保存本地接收时间,就无法判断行情在网络中停留了多久。如果只保存交易所时间,又无法知道本地系统何时真正收到数据。两者都需要保留。

时间戳精度也不是显示格式。把毫秒格式化成微秒,不会增加真实精度。系统时钟必须与交易所时钟保持一致,时间同步机制需要持续监控。出现时钟漂移时,不能继续把本地时间当作事件发生时间。

建议在原始行情层保留至少以下字段:

  • 原始交易所时间戳。
  • 本地接收时间戳。
  • 消息序号。
  • 品种代码。
  • 行情类型。
  • 价格与数量。
  • 连接编号或会话编号。
  • 数据源版本。

原始数据不能在第一步就覆盖。标准化数据可以重新生成。原始数据一旦丢失,后续无法复核。

Tick 连续性比数据量更重要

数据很多,不代表数据连续。

需要检查:

1. 消息序号是否连续。

2. 同一时间戳下的消息排序是否稳定。

3. 断线重连后是否存在区间缺失。

4. 快照与增量消息是否能够正确拼接。

5. 价格跳变是否来自真实成交,还是来自丢包。

6. 深度档位变化是否能够重建订单簿。

7. 长连接运行期间是否存在重复消息。

没有说明的缺失,不能直接当作没有缺失。行情供应商可能只提供快照。也可能提供增量流。两者的数据结构不同。使用快照模拟订单簿,无法还原真实的队列变化。

对于均值回归策略,缺失一个局部 Tick 可能改变短期价格偏离值。对于订单流策略,缺失一段盘口事件会直接破坏特征。回测中未发生的成交,可能只是数据缺口造成的假象。

端到端延迟要持续采样

行情延迟需要在长连接状态下测量。不能只在启动时测一次。

测试应覆盖:

  • 正常交易时段。
  • 开盘和收盘附近。
  • 行情消息密集时段。
  • 网络重连过程。
  • 多品种并发订阅。
  • 本地进程负载升高时。
  • 数据落库与实时计算并行时。

测量结果要保存原始样本。不要只记录平均值。延迟分布的尾部通常比均值更能解释实盘偏差。

如果一个数据源平均延迟低,但九十九百分位频繁出现尖峰,那么策略实际执行可能并不稳定。相反,一个平均值略高但分布平滑的数据源,可能更适合需要稳定触发的日内策略。

先把行情层与策略层分开

行情接入模块不应直接修改策略状态。

合理的分层至少包括:

  • 原始行情接入层。
  • 时间戳与序列校验层。
  • 标准化行情层。
  • 订单簿重建层。
  • 特征计算层。
  • 策略决策层。
  • 订单执行层。

这样做不是为了增加类和接口。是为了保留可复核性。

策略结果异常时,需要知道异常来自数据源、时间排序、订单簿重建,还是信号计算。如果所有逻辑写在一个回调函数里,最终只能重新运行。无法定位。

柜台穿透时延,要看完整路径

柜台是交易系统的边界。策略进程发出订单,不代表订单已经进入交易所撮合链路。

所谓柜台穿透延迟,至少涉及订单从本地交易程序、网络链路、柜台服务,到交易所接口的过程。供应商给出的单点参数不能替代端到端测量。

国内券商和期货市场存在极速柜台方案,例如 XTP,以及基于 FPGA 的硬件柜台。部分方案可以把传统柜台的百微秒级穿透延迟降至个位数微秒级。已披露的示例范围约为 5.1 微秒至 9.5 微秒。

这个数字有价值。但不能脱离条件使用。

需要问清楚四个边界

第一,测量起点在哪里。

是策略程序调用发送接口的时刻,还是网卡真正发包的时刻。两者不同。接口调用可能还经过线程队列、内存复制和用户态缓冲。

第二,测量终点在哪里。

是柜台收到订单,还是交易所接口收到订单。是交易所回报,还是策略进程收到回报。终点不同,结果不能比较。

第三,测试订单是什么。

不同订单类型、不同品种、不同风控规则,路径可能不同。撤单、改单、开仓和平仓不一定使用同一条处理路径。

第四,是否包含事前风控。

如果宣传参数排除了风险检查,那么它只是柜台内部转发时间。真实交易链路仍需要计算持仓、资金、价格、数量和权限约束。

事前风控不能从热路径消失

高频系统会试图压缩所有步骤。风控不能因此被旁路。

风险检查通常包括:

  • 单笔数量限制。
  • 价格偏离限制。
  • 资金和保证金检查。
  • 持仓限制。
  • 品种和账户权限检查。
  • 撤单频率限制。
  • 订单方向和开平标识校验。
  • 交易时段校验。

这些检查如果全部通过远程服务完成,会增加网络往返和队列等待。高频热路径通常会把可确定的规则下沉到本地。需要共享状态的复杂规则,则要设计低延迟状态同步机制。

Rust 或 C++ 适合承担订单执行和事前风控等热路径。原因不是语言标签。是内存管理、运行时行为和延迟可控性。Python 适合研究建模、策略验证和数据分析,但不应默认承担高频极速交易的全部链路。

不能把所有代码强行写成一种语言。也不能把系统拆成大量远程微服务,再期待微秒级结果。服务边界越多,序列化、网络传输和调度开销越难隐藏。

物理距离仍然存在

柜台方案再快,也不能消除网络路径。

机房位置、物理链路、网卡配置、交换机转发、操作系统内核和进程绑核,都会影响最终结果。所谓低延迟,需要说明部署位置和网络拓扑。

应当获得一张可复核的链路清单:

  • 策略服务器所在位置。
  • 行情接入位置。
  • 柜台所在位置。
  • 交易所连接位置。
  • 中间交换设备。
  • 网络接口类型。
  • 是否经过共享服务。
  • 是否经过代理或消息队列。
  • 测试时的并发订单量。
  • 测试时的行情负载。

如果这些条件没有披露,单个微秒数字只能作为实验室结果。不能直接转化为策略收益假设。

热路径与研究路径必须分离

交易软件开发中,Python、Rust 和 C++并不是相互替代的选项。它们承担不同的系统责任。

研究路径需要高迭代速度。需要快速读取数据、构造因子、修改参数和分析结果。Python 在这里有效。

热路径需要确定性。需要控制内存分配、线程行为和运行时暂停。Rust 或 C++更适合订单执行、风控拦截和低延迟数据处理。

推荐的分层方式

研究层负责:

  • 数据清洗。
  • 因子构造。
  • 参数扫描。
  • 回测报告。
  • 统计检验。
  • 模型比较。
  • 交易成本分析。

核心引擎负责:

  • 行情事件解析。
  • 时间排序。
  • 订单簿更新。
  • 策略状态推进。
  • 订单生成。
  • 风控判断。
  • 委托状态管理。
  • 成交回报处理。

基础设施层负责:

  • 网络连接。
  • 日志写入。
  • 数据缓存。
  • 监控告警。
  • 会话恢复。
  • 数据落盘。
  • 故障切换。

研究层可以调用核心引擎。核心引擎不应依赖研究环境才能运行。

如果实盘策略重新用另一套代码实现,逻辑漂移几乎不可避免。变量初始化顺序、成交处理时点、撮合规则和边界条件都可能不同。回测结果因此失去可比性。

Python 不应承担所有责任

全 Python 方案可以用于低频策略。也可以用于原型和验证。问题不在于能否运行,而在于运行边界。

如果策略每秒处理少量事件,Python 的开发效率通常更有价值。如果系统需要持续处理大量 Tick,且延迟要求进入毫秒或微秒级,解释器开销、对象分配、垃圾回收和第三方库调用都需要单独测量。

不要用一次空载基准测试证明性能。应当观察:

  • 单条行情处理时间。
  • 批量行情处理时间。
  • 高并发订阅下的队列长度。
  • 内存分配次数。
  • 垃圾回收暂停。
  • 线程切换次数。
  • 日志写入对热路径的影响。
  • 数据落盘对实时处理的影响。

热路径上的日志不能无限增加。详细日志可以异步发送,但关键事件必须保留可追踪标识。否则延迟与可审计性会互相冲突。

不要过早引入复杂中间件

消息队列、远程调用和多层微服务适合解决扩展性问题。它们不天然适合低延迟。

每个中间层都可能增加:

  • 序列化时间。
  • 内存复制。
  • 网络往返。
  • 队列排队。
  • 服务发现。
  • 重试逻辑。
  • 故障恢复路径。

低频系统可以接受这些开销。微秒级热路径通常不能接受。

更合理的做法是把实时路径和非实时路径分开。实时路径只保留必要的内存数据结构和同步操作。报表、监控、历史记录和策略分析走异步路径。

这不是追求代码最少。是控制不可预测行为。

回测与实盘,必须复用核心引擎

回测赚钱、实盘亏损,不一定是策略失效。也可能是两个系统执行了不同的逻辑。

常见偏差来源包括:

  • 回测使用收盘价,实盘使用逐笔价格。
  • 回测默认订单立即成交,实盘存在排队。
  • 回测没有模拟盘口深度。
  • 回测忽略撤单延迟。
  • 回测没有处理部分成交。
  • 回测使用未来可见数据。
  • 回测与实盘的时间戳排序不同。
  • 回测没有复现风控拒单。
  • 回测没有复现断线和重连。
  • 回测使用理想化滑点。
  • 实盘重新实现策略,状态机产生差异。

这些问题不能用增加一个滑点参数解决。

Tick 级仿真是最低要求之一

如果策略依赖日内价格路径,分钟线通常不够。至少需要 Tick 级事件。若策略依赖盘口变化,还需要订单簿深度仿真。

订单簿仿真要处理:

  • 各档价格。
  • 各档数量。
  • 委托进入顺序。
  • 成交消耗。
  • 订单排队位置。
  • 部分成交。
  • 撤单事件。
  • 深度更新。
  • 快照与增量的衔接。

如果回测只判断某根 K 线的最高价和最低价,就无法确定订单是否真的成交。价格触及不等于订单成交。成交还取决于队列和可用数量。

对于均值回归策略,这个差异尤其明显。价格短暂偏离均值时,信号可能只存在几个 Tick。回测可以在理想价格成交。实盘订单却可能排在队列后部。

回测和实盘共用状态机

策略核心应当以事件驱动方式运行。

输入是行情事件、成交回报、撤单回报、拒单回报和系统事件。输出是订单意图或风险决策。回测和实盘只替换事件来源与订单执行适配器,不替换策略状态机。

可以将系统拆为:

  • 事件接口。
  • 策略状态。
  • 订单状态。
  • 风控状态。
  • 撮合适配器。
  • 实盘柜台适配器。
  • 回测数据适配器。

回测使用历史事件。实盘使用实时事件。两者进入同一套核心逻辑。

这样可以比较同一事件下的状态变化。也可以记录每一步输入和输出。出现差异时,定位范围会明显缩小。

回测与实盘不应共享一份结果。应共享一套事件处理逻辑。

用确定性重放定位偏差

每次回测都应保存版本信息和参数。至少包括:

  • 行情数据版本。
  • 数据清洗规则。
  • 策略代码版本。
  • 核心引擎版本。
  • 风控规则版本。
  • 交易成本参数。
  • 撮合规则。
  • 参数配置。
  • 时间窗口。
  • 运行环境。

实盘异常后,使用同一批原始事件重放。目标不是重新生成一份收益曲线。目标是比较订单、状态和风控结果。

如果相同事件输入无法得到相同决策,说明系统存在非确定性。原因可能是线程竞态、时间读取方式、未排序事件、随机数种子或外部状态依赖。

没有确定性重放,调试只能依赖日志猜测。猜测不属于测试方法。

软件测试不能只测功能正确

交易系统测试分为功能、性能、数据和故障四类。四类都需要。

功能测试

功能测试确认状态机是否符合规则。

例如:

  • 收到成交回报后,持仓是否正确更新。
  • 部分成交后,剩余数量是否保留。
  • 撤单成功后,订单状态是否终止。
  • 拒单后,策略是否停止重复发送。
  • 连接断开后,未确认订单是否进入待核验状态。
  • 重复回报是否不会重复增加持仓。
  • 跨交易日后,状态是否正确初始化。

这些测试不需要真实市场。可以使用构造事件。

性能测试

性能测试需要建立负载模型。不能只测试单条消息。

至少模拟:

  • 多品种并发行情。
  • 行情突发增长。
  • 多策略同时触发。
  • 订单批量生成。
  • 风控状态频繁更新。
  • 日志和数据落盘并行。
  • 断线重连后的数据补发。

测试输出应包括吞吐量、处理延迟、队列长度、丢包数、重复消息数和错误率。

接口成功率可以作为运营指标。目标可以设定为 99%以上,但不能把成功率目标当作实际达成结果。需要按接口、时段、错误类型和重试次数拆分。

故障测试

交易系统不会只在正常状态下运行。

需要主动制造故障:

1. 断开行情连接,观察是否识别缺口。

2. 延迟成交回报,观察策略是否重复下单。

3. 让柜台返回拒单,观察状态机是否收敛。

4. 中断数据库,观察实时路径是否继续运行。

5. 降低网络质量,观察延迟尾部。

6. 重启策略进程,观察订单状态是否恢复。

7. 注入乱序消息,观察系统是否拒绝或重排。

8. 注入重复消息,观察持仓是否重复累计。

故障处理不应停留在异常日志。需要定义系统状态。

例如,订单发送后没有回报,不能直接认为订单失败。它可能已经进入交易所,只是回报链路中断。此时系统应进入待核验状态,而不是立即重发。

时钟测试不能省略

金融事件排序依赖时间。微秒级交易更依赖时间。

要检查:

  • 本地时钟与交易所时间的偏差。
  • 多台服务器之间的时钟差异。
  • 时钟同步失败后的告警。
  • 时间回拨时的事件排序。
  • 时间戳精度是否真实。
  • 日志时间与行情时间是否使用同一标准。

系统可以使用单调时钟计算耗时。事件排序则保留交易所时间、序列号和本地接收时间。不要用墙上时钟直接计算延迟。时钟调整可能造成负数或异常跳变。

架构选择的成本,不只在延迟

低延迟架构通常增加开发、部署和维护成本。

Rust 或 C++热路径需要更严格的内存管理和并发设计。FPGA 柜台需要硬件适配和更复杂的测试环境。专用机房和低延迟网络会增加基础设施成本。多语言系统还会增加接口维护成本。

因此,技术选型需要同时计算四种成本:

  • 性能成本。是否达到延迟和吞吐目标。
  • 正确性成本。是否能够复现、审计和恢复。
  • 维护成本。是否有足够能力修改热路径。
  • 迁移成本。未来更换柜台、数据源或部署位置时,系统是否可拆分。

单纯追求最低延迟,可能得到一个无法快速迭代的系统。单纯追求开发速度,可能得到一个无法稳定执行的系统。

选择时可以按这个顺序排查

1. 先确定策略频次。

把策略归入秒级、毫秒级或微秒级。不要从供应商技术名词开始。

2. 建立端到端延迟预算。

将行情、计算、风控、发单、柜台和回报分别测量。

3. 要求供应商披露测试边界。

记录起点、终点、订单类型、并发量、部署位置和网络路径。

4. 核验行情时间戳。

同时保存交易所时间和本地接收时间。检查时钟同步与序列连续性。

5. 检查 Tick 数据与订单簿能力。

确认是否支持缺口识别、快照重建、增量拼接和深度仿真。

6. 划分研究路径和热路径。

Python 用于研究和验证。Rust 或 C++用于需要确定性和低延迟的执行链路。

7. 确认回测与实盘是否复用核心引擎。

只替换数据源和执行适配器。不重写策略状态机。

8. 进行压力与故障测试。

测试长尾延迟、断线、重复回报、乱序事件和状态恢复。

9. 建立确定性重放机制。

保存原始事件、版本、参数和执行结果。异常可以重现。

10. 最后才比较硬件和柜台。

如果策略本身只需要秒级响应,微秒级硬件不会自动产生额外收益。

结论:先证明需要低延迟,再建设低延迟

选择交易技术,本质上是选择一组可测量的约束。

秒级策略需要稳定。毫秒级策略需要低抖动。微秒级策略需要控制热路径、网络路径和柜台路径。三者不是同一个问题。

行情数据质量决定输入是否可信。柜台穿透时延决定订单是否及时进入交易链路。风控设计决定低延迟是否以牺牲正确性为代价。核心引擎复用决定回测结果能否迁移到实盘。

技术选型的最终判断,不应写成某种语言更快、某个柜台更快或某台服务器更快。应当写成一组测试结果:

  • 在什么行情负载下运行。
  • 端到端延迟分布如何。
  • 尾部是否可控。
  • 数据缺口能否识别。
  • 订单状态能否恢复。
  • 回测与实盘是否得到同一事件结果。
  • 代码在参数变化后是否仍然可复核。

参数优化也应遵循同一原则。不要只优化收益。同步观察延迟分位数、成交率、拒单率、滑点、撤单比例和异常恢复时间。不要把回测中的理想成交当作模型能力。不要把最低延迟当作系统常态。

模型仍然有边界。数据缺失、极端行情下的最大滑点、特定机房的真实物理延迟,都需要独立样本验证。没有样本,就保留未知。

系统先做到可测量。再做到可复现。最后才讨论是否需要更快。

Related reading: 量化交易服务器的部署抉择:物理托管与云服务器的延迟与成本博弈 and Python量化系统真的太慢吗:延迟瓶颈分析与C++重构时机.

常见问题

如何根据策略频次确定延迟预算?
基本面低频策略通常需要秒级响应;日内趋势跟踪和均值回归主要在毫秒级;高频做市与订单流交易则需进入微秒级约束。策略频次与系统延迟不匹配会导致过度建设或性能不足。
为什么交易系统不能只看平均延迟?
平均延迟只能描述中心位置,而交易系统更需要观察最大值、分位数和长尾。一次偶发的延迟尖峰可能发生在订单有效期内,这对高频策略的影响远大于均值。
行情数据接入时为什么要保留多个时间戳?
行情可能存在交易所生成、网关转发、本地接收等多个时间点。同时保留交易所时间和本地接收时间,才能准确判断行情在网络中的停留时间,并监控时钟漂移。
为什么回测赚钱但实盘亏损?
常见原因包括回测与实盘使用了不同的逻辑、忽略了盘口深度、未模拟撤单延迟或部分成交,以及回测使用了未来数据。确保回测与实盘共用同一套核心事件处理逻辑是减少偏差的关键。
高频交易中如何处理事前风控?
风控不能被旁路,高频热路径通常会将可确定的规则下沉到本地执行。对于需要共享状态的复杂规则,应设计低延迟的状态同步机制,避免因远程服务调用增加网络往返开销。