
高频量化交易系统架构图,展示逐笔数据管线、Alpha因子引擎和订单执行流程
从零搭建高频量化交易系统:微观结构、逐笔数据与Alpha因子实战指南
聊高频交易的人多,真正搭过系统的少。跟你说HFT就是"拼速度""做colo""拉光纤到交易所"的,那叫维基百科摘要,不叫工程现实。
真相是:HFT 10%靠策略,90%靠基础设施。让你赚钱的Alpha信号可能就二十行Python。但能让这二十行代码跑得够快、够稳、够安全地去捕捉那个Alpha的系统——才是真正的工程量所在。
我在数据管线和延迟调优上花的时间比愿意承认的多得多。这篇指南走一遍搭建HFT系统的真实路径——从市场微观结构到连续报价策略——那些你从"好看的回测"到"真能赚钱的东西"之间必须搞定的环节。
系统架构:一个HFT系统到底由什么组成
HFT不是一个程序,是一串专业化组件的管线,每个环节要求完全不同。拆开来看:
数据接入层——你在接一个消防水龙头。每一笔报价更新、每一笔成交、每一次订单簿变化,时间戳精确到毫秒甚至更细。这块必须快、不丢数据、有序。丢一个tick就可能让你的订单簿重建整个崩掉。
订单簿重建——交易所原始推送给你的是增量(deltas),你得从增量重建完整的簿。重建逻辑写错了,下游所有信号全是垃圾。我有一次花了三天调"策略有问题",最后发现是订单簿重建的价格层索引差了一位。
信号引擎——你的Alpha因子住的地方。拿当前簿状态+近期成交流,计算信号,输出预测。这是大家最爱聊的部分。它在代码库里大概占20%。
策略层——把信号变成下单决策。"信号说价格要涨→挂买单"。入场、出场、库存管理、风控规则都在这。
执行层——把订单路由到交易所,管理挂单、撤单、改单。延迟敏感到令人发指的程度。
风控与监控——持仓限制、日内亏损限制、熔断开关、盈亏归因、延迟仪表盘。你希望它很无聊,但出事的时候你会非常庆幸它存在。
每个环节的延迟预算不同。数据接入要亚毫秒级。信号计算可以容忍几毫秒。执行层每一微秒都在疼。风控检查要快但更得对——你不能因为着急就跳过风控。
市场微观结构——价格形成的物理学
如果你来自传统量化背景,忘掉大部分时序建模的套路。HFT不在分钟线或日收益上运作。它运作在微观结构层面——订单撮合的机制和价格形成的力学。
核心概念:
限价订单簿——一个排着队的买卖挂单队列,分布在不同价位上。簿就是市场。理解它的形状、深度和动态是第一步。
价格形成——价格不是因为"情绪"动的。在微观层面,它动是因为订单流不平衡超过了当前最优价位的可用流动性。主动买比卖方深度多?价格跳一档。就这么机械。
价差动态——买卖价差不是常数。波动大的时候展宽,竞争激烈的时候收窄。做市商竞争着提供最窄价差。价差是你提供流动性的风险补偿。
订单流不平衡(OFI)——被研究最多的微观结构信号。最简版就是:(主动买量 − 主动卖量)。偏离零的时候,价格倾向于跟随。不是唯一的信号,但确实是跨市场持续有效的那一个。
我刚接触微观结构数据的时候很惊讶——短期价格变动里有多少是纯机械性的,由撮合规则和队列动态驱动,而不是"信息"。很大一部分短期价格变化就是簿在重新平衡。这是可以被利用的。
从"基础价格模型"到"改进价格模型"的演进本质上就是:从对称随机游走开始,加入订单流不平衡作为漂移项,加入队列位置动态,加入逆向选择效应。每一层解释更多的方差。
逐笔数据:你的原材料
HFT的一切始于逐笔数据。不是K线,不是分时,是每一笔成交和报价,按序排列。
你在处理什么:
- 成交tick:价格、量、时间戳、主动方向(买/卖发起)
- 报价tick:最优买、最优卖、各层挂单量、时间戳
- L2深度:完整订单簿状态(或增量用于重建)
难点不在获取——大多数交易所卖数据或通过API提供。难点在规模化处理。
一个流动性好的期货合约每天大约产生10万到50万笔tick。跨多个品种、数月历史,就是几十亿行。这个量级下:
- CSV文件是笑话。别试。
- 标准pandas过了几百万行就喘了。
- 需要列式存储(Parquet、Arrow)或专门格式(有些团队用自研二进制格式)。
- 回测逐笔策略需要按序重放数据,随机访问不是朋友。
这是个最小化的Python逐笔数据接入示例,用流式处理避免内存炸裂:
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
def process_tick_stream(tick_file, chunk_size=1_000_000):
"""分块处理逐笔数据,避免内存爆炸"""
reader = pd.read_csv(tick_file, chunksize=chunk_size,
usecols=['timestamp', 'price', 'quantity', 'side'])
chunks = []
for chunk in reader:
chunk['timestamp'] = pd.to_datetime(chunk['timestamp'], unit='ns')
chunk = chunk.sort_values('timestamp')
chunks.append(chunk)
df = pd.concat(chunks)
return df.reset_index(drop=True)
def compute_ofi(trades_df, window_ms=100):
"""计算滚动窗口内的订单流不平衡"""
trades_df = trades_df.set_index('timestamp')
# 带符号成交量:买为正,卖为负
trades_df['signed_vol'] = trades_df['quantity'] * (
1 if trades_df['side'] == 'buy' else -1
)
ofi = trades_df['signed_vol'].rolling(f'{window_ms}ms').sum()
return ofi
这是玩具代码——生产系统热路径用C++、Rust,至少是编译Python(Cython、Numba)。但它展示了问题的形状:流式输入、排序、时间窗口聚合、输出信号。
存储决策比人们以为的重要得多。我见过团队因为把逐笔数据存在一个"好查询"但读吞吐跟不上的数据库里,白白浪费好几天。Parquet文件加上合理的分区方案(按日期、按品种)能让你走很远。
构建不会坍塌的Alpha因子
关于HFT Alpha一个不舒服的事实:大部分因子有效一段时间然后就死了。市场会适应,对手会发现同样的信号,Edge会衰减。
所以目标不是找到一个神奇因子,而是建一条因子构建管线,让你能高效地发现、测试、部署信号。
流程:
- 假设——从微观结构的逻辑出发。"订单流不平衡预测下一秒收益,因为主动成交单消耗流动性并推动中间价。"好。"让我试47种均线组合"——不好。
- 特征构建——把假设变成可计算的信号。OFI:滚动窗口内主动买入量的带符号求和,按平均成交量标准化。簿不平衡:(买量 − 卖量) / (买量 + 卖量)。简单、可解释、计算快。
- 测试——算信息系数(IC),即你的信号和未来收益的相关性。tick级数据IC > 0.02就有意思了。IC > 0.05算强的。测衰减:信号的预测能力多快消失?如果50ms内有用来200ms就废了,那直接塑造你的执行策略。
- 组合——单一因子撑不住。建一个模型(线性、树模型,随便)把多个因子按学习到的权重组合。模型本身不如输入的因子重要。
- 样本外验证——一月到三月表现漂亮的因子,四月可能完全死了。滚动测试不是可选项。如果某因子只在一个市场状态下有效,那不叫因子,叫过拟合。
经典HFT Alpha分类:
- 订单流信号——OFI、成交强度、主动方比率
- 簿形信号——深度不平衡、队列位置、价差动态
- 跨品种信号——相关品种间的领先滞后关系
- 事件信号——数据发布、大单成交、簿回补的反应
区分能赚钱的量化团队和业余玩家的不是信号多精巧。是有纪律地杀掉失效因子、并且有管线去找新因子。
连续报价:能赚钱的策略(如果你活下来的话)
连续报价是做市HFT的看家本领。不预测价格方向,而是在你估计的公允价值两侧持续挂买卖双边。赚价差。亏在被信息方狩猎。
核心循环:
- 估计公允价格(中间价 + 信号漂移)
- 挂买在公允 − 半价差,挂卖在公允 + 半价差
- 买被成交→你现在持多仓。调整:卖单加宽,买单上移(鼓励平仓)
- 卖被成交→持空仓。镜像操作
- 簿变更新、信号更新、持仓超限时撤单重挂
- 循环。每秒几百次。
数学很简单。执行很残酷。
逆向选择是杀手。有人主动吃你的买单,可能是因为他知道你不知道的信息。你刚在一个可能是坏价格的位置买了。管理方法:严格的持仓限制、够快的信号更新(让你的报价在新信息来时先于被狩猎而更新)、以及在检测到信息方时加宽价差。
这是个报价逻辑的玩具实现:
class SimpleMarketMaker:
def __init__(self, max_position=100, half_spread=0.5):
self.position = 0
self.max_position = max_position
self.half_spread = half_spread
def on_book_update(self, mid_price, signal=0.0):
"""每次簿更新时生成报价"""
# 信号调整公允价格(Alpha漂移)
fair_price = mid_price + signal
# 持仓倾斜——移动报价以减仓
inventory_skew = (self.position / self.max_position) * self.half_spread
bid = fair_price - self.half_spread - inventory_skew
ask = fair_price + self.half_spread - inventory_skew
# 到持仓限制不挂单
if self.position >= self.max_position:
bid = None
if self.position <= -self.max_position:
ask = None
return {'bid': bid, 'ask': ask}
def on_fill(self, side, price, quantity):
"""成交时更新持仓"""
if side == 'buy':
self.position += quantity
else:
self.position -= quantity
这能跑。这不会赚钱。除非你加上:真实的延迟建模、逆向选择模拟、手续费核算,以及一个真有Edge的信号。但它展示了结构——每个连续报价系统,核心都是这个循环,上面叠越来越复杂的旋钮。
跳到"跳价资产"(价格按最小tick离散跳动而非连续运动的品种)又加一层。在跳价市场里,报价逻辑必须尊重最小tick单位,价差优化从连续问题变成离散问题。小细节,对盈亏大影响。
基础设施税:延迟、存储和算力
一直绕着说,直接讲:HFT策略在生产线失败的原因通常不是策略,是基础设施。
延迟预算——从数据到达你的服务器到你的订单到达交易所,有一个窗口。如果这个窗口比你对手的长,你就输了。不是因为你信号差——是因为他用同样的信息比你快一步行动。
典型预算拆解:
- 网络入口:10–50μs(硬件决定)
- 解析和标准化:5–20μs
- 簿更新:5–10μs
- 信号计算:10–50μs
- 决策逻辑:1–5μs
- 订单序列化+网络出口:20–100μs
总计:50–250μs。如果你的对手50μs走完,你花200μs,你每挂一单都在被逆向选择。
存储——逐笔数据很大。回测要重放。需要快的顺序读,不是随机访问。NVMe SSD、内存映射文件、或者热数据集留内存。一个月单品种逐笔数据可以50–200GB。最近N天留内存,其余放快存。
算力——信号引擎要逐笔实时处理。Python在热路径太慢。常见选择:
- C++做核心接入和执行(确定性、低延迟)
- Python做研究、回测、非关键路径
- Rust在崛起——内存安全,没有GC暂停
网络——内核旁路(DPDK、Solarflare OpenOnload)跳过OS网络栈。通过交易所提供的API直连市场接入。如果你认真玩,交易所数据中心co-location。到这一步就不再是软件
...(truncated)...

评论0