量化交易系统的四层逻辑分层:为什么混层比 Bug 更危险
混层,才是量化系统的真正敌人
很多人第一次写量化策略时,思路是这样的:
「先把主逻辑跑通,之后再重构。」
这在大多数软件工程里是可行的。但量化系统是例外——它是少数几种重构窗口期非常短的系统之一。
原因很简单:「还没重构」的那段时间里,系统是真实上线跑着的,用的是真实资金。一旦信号层里藏了一个数据库写入,或者执行层的状态机里混进了一段技术指标计算,下一次高波动行情就是系统性事故的火药桶。
这篇文章拆解的不是某个策略算法,而是一个专业量化系统的逻辑分层骨架——它把系统切成四个高内聚、低耦合的层次,每层只做一件事,每层之间只用明确定义的数据结构通信。
四层结构总览
如上图所示,整个系统数据流单向向下,每一层只能消费上一层产出的标准化数据,不能反向调用,也不能跨层直接通信。这不是风格偏好,而是系统可靠性的基础。
Layer 1:数据接入层——"眼睛与耳朵"
职责:与交易所建立连接,把混乱的原始数据翻译成系统内部的标准语言。
币安(Binance)的 WebSocket 行情消息长这样:
{
"e": "depthUpdate",
"E": 1720000000000,
"s": "BTCUSDT",
"b": [["65000.00", "1.5"]],
"a": [["65001.00", "0.8"]]
}
Hyperliquid 的格式则完全不同,是一套自定义的 JSON schema。如果信号层或策略层需要直接处理这些原始数据,代码里就会出现 if exchange == "binance" 这样的分支——而这个分支迟早会失控。
数据接入层的核心工作是归一化(Normalize):把所有交易所的 Ticker、Depth、Trades 统一转换成系统内部的 OrderBook 对象。上层从此只见标准结构,不见任何交易所私货。
这一层最关注的是:吞吐量、低延迟、断线重连、连接心跳监控。连接池管理是它的核心技术问题,不是业务问题。
Layer 2:计算信号层——"大脑"
职责:接收标准化的 OrderBook,在纯内存中完成计算,吐出信号(Signal)。
这一层有一条铁律:
不能有任何副作用。 不能写数据库,不能发网络请求,不能调用下单接口。
这条铁律让信号层成为整个系统里最容易测试的部分——给定一个 OrderBook 输入,总能得到确定性的输出。
实践中的信号层函数长这样:
def mid_spread_signal(orderbook: OrderBook) -> float:
"""计算多档位 VWAP,返回双边价差信号"""
bid_vwap = sum(p * v for p, v in orderbook.bids[:5]) / sum(v for _, v in orderbook.bids[:5])
ask_vwap = sum(p * v for p, v in orderbook.asks[:5]) / sum(v for _, v in orderbook.asks[:5])
return (ask_vwap - bid_vwap) / bid_vwap # 价差比率
def funding_signal(rate_a: float, rate_b: float) -> float:
"""计算两边交易所的资金费率 Gap"""
return rate_a - rate_b
注意:这两个函数没有任何 import requests、没有 db.session、没有任何 I/O。它们是无状态的纯函数,可以在微秒级完成运算。
Layer 3:策略决策层——"指挥官"
职责:拿到信号之后,决定「做不做」和「做多少」。
策略层内部有三道关卡:
① 防抖(Debounce)
行情数据的刷新频率远高于策略执行的频率。200ms 内如果价格从 65000 跳到 65500 再跳回 65000,这不是机会,这是噪声。
防抖窗口的作用是:把时间窗内同向的信号合并,过滤单点毛刺,避免在一个毛刺上连续触发多次下单。
② 风控(Risk Control)
这是系统的硬边界。无论信号多强,如果当前持仓已经达到 Tier 上限,这笔交易就会被硬拒绝。
风控检查通常包括两个维度:
- 单品种维度:某个交易对的当前净暴露是否超过该品种的 Tier 限制
- 全系统维度:所有品种的总名义价值是否超过全系统的最大持仓上限
③ 仓位计算(Sizing)
不是固定手数,而是动态计算:根据当前两边盘口各 5 档的 VWAP 极限,反推这次最多能对冲多少名义价值而不会因为冲击成本导致策略失效。
Layer 4:执行对冲层——"拳头"
职责:把决策转化为真实订单,并用状态机保证对冲安全。
这一层是整个系统里最复杂的,因为它要处理真实世界的不确定性:网络延迟、部分成交、超时、交易所故障。
状态机(Order FSM)是核心
状态机的关键能力是知道自己处于哪个状态,并在任何意外情况下做出正确反应。
以一个典型的险情为例:
A 边(Binance 做空)已经成交了 80%,此时网络中断。
如果没有状态机,系统重启后可能不知道这 80% 已经成交——要么不去对冲 B 边,要么重复下单 A 边。两种情况都是单边裸奔,会造成真实亏损。
有了状态机,系统的处理逻辑是:
- 检测到网络中断 → 状态进入
A_PARTIAL(已部分成交) - 触发 Fallback 逻辑:撤销 A 边剩余订单
- 以 A 边已成交的名义价值为基准,在 B 边(Hyperliquid)发出市价追单
- 容忍一定的 slippage,但确保对冲名义值匹配,风险清零
Fallback 的设计哲学是:安全优先于利润。宁可多付几个基点的市价追单成本,也绝不接受单边暴露过夜。
为什么不能等「跑通后再重构」
回到开头的问题:为什么量化系统没有「先跑通再重构」的窗口期?
因为这四层的约束是互相依赖的:
| 层级 | 关键约束 | 违反后果 |
|---|---|---|
| 信号层 | 纯函数,无副作用 | 微秒级计算变成毫秒级,延迟累积导致信号过时 |
| 策略层 | 防抖 + 风控串联 | 毛刺上触发真实订单,风控失效后持仓无上限 |
| 执行层 | 状态机追踪成交 | 部分成交后单边裸奔,真实亏损 |
这三个约束一旦混杂,重构的成本不是「工程师多加班几天」,而是在重构完成之前,系统处于一种既不正确又在真实跑单的状态。这个状态持续的每一秒都是风险。
分层的本质不是架构美学,是让每一层的正确性可以独立验证——信号层用单元测试,策略层用历史回测,执行层用沙盒模拟。任何一层出了问题,定位范围就是那一层,而不是整个系统。
结语
专业量化系统和玩具级脚本的分界线,不在于策略有多复杂,而在于是否在一开始就把职责边界划清楚。
四层分层是一个可以落地的起点:数据接入层管连接,信号层管计算,策略层管决策,执行层管安全。四层各司其职,混层的那一刻就是系统可靠性开始崩塌的那一刻。
Comments
Post a Comment