我做了快五年量化策略研究,前两年还沉迷在挖掘传统因子里,后来发现自己一直在跟换手率和拥挤度较劲。真正让我转向强化学习(RL)方向的契机,是某次实盘复盘时发现:同一个因子组合,在高频场景下用不同方式进单,收益差距能达到一倍以上。那一刻我才意识到,预测信号和交易决策之间有一条巨大的鸿沟,而订单簿数据恰好是这条鸿沟上最关键的桥梁。
这篇文章想跟你聊聊我过去一年多在“RL + 订单簿建模”这个方向上踩过的坑、验证过的思路、以及最终沉淀下来的一套可复用的工程方法。核心会聚焦在:订单簿数据如何用结构化方式送进RL模型、状态动作空间怎么设计才不反直觉、回测时哪些隐患最容易被忽视。无论你是刚开始接触量化的新手,还是已经在传统模型里打转很久的从业者,这篇文章都值得你花二十分钟读完,它至少能帮你省掉我当初几个月的弯路。
严格来说,“RL+订单簿”不是单一技术问题,而是一个包含数据工程、环境设计、奖励塑形和风险管理四个子问题的系统。我见过太多人把精力全花在模型结构上,结果数据预处理粗糙、奖励函数漏洞百出,最后Models怎么调都赚不了钱。下面我按实际操作流程,从数据到部署逐个环节展开讲。
1. 订单簿数据到底长什么样:Level 2结构的工业级拆解
想用RL做订单簿建模,第一个绕不开的问题就是:你说的“订单簿”究竟是哪一层数据?很多刚入门的同学以为订单簿就是买一卖一价,实盘数据拿到手才发傻——量级完全不同。
1.1 从Level 1到Level 3:你能拿到什么数据
Level 1是最常见的行情快照,通常只有最优买价/卖价、最优买卖量、最新成交价和成交量。这类数据做简单的盘口方向判断勉强够用,但喂给RL模型就太单薄了——你对市场微观结构的观察维度严重缺失。
Level 2是逐笔委托数据的聚合视图,包含从买一档到买N档、卖一档到卖N档的价格和挂单量,每笔订单的增删改事件也能通过增量数据还原出来。我实盘项目里用的就是Level 2的十档行情,每秒大约有10到20个快照,加上增量更新,一天的原始数据量在2到5GB左右。
Level 3则更进一步,能精确到每个订单的ID、时间、价格、数量、方向以及它是新单、撤单还是成交。数据量级比Level 2再大几十倍,很多券商接口虽然声称支持,实际推送延迟和丢包率你都未必扛得住。我的建议是:个人研究阶段不要碰Level 3,先把Level 2用扎实已经能跑出很漂亮的结果了。
1.2 订单簿快照的字段语义与更新机制
Level 2快照数据里最容�易被忽视的是时间戳精度和更新类型。不同交易所的时间戳粒度差异很大,有的是秒级,有的是毫秒级,少数支持微秒级,这个差异直接影响你计算订单簿变化速度的准确性,比如说订单到达速率、撤单比例、价差变动频率这些特征,时间戳一粗糙全都不准。
第二个容易忽略的点是快照数据的“脏”现象。交易所推送快照时,偶尔会出现买卖价倒挂(买一价高于卖一价,通常是极速行情下中间价跳动导致的串帧)、挂单量为负(增量更新未对齐)、或者买卖盘口为空(熔断停牌时)等异常。这些脏数据对传统因子模型影响有限,因为你有各种去极值手段,但对RL模型是致命的,直接喂进去可能会让模型学到疯狂报买单的错误策略。
所以工业级订单簿建模的第一步不是特征工程,而是数据清洗:剔除价差为负的快照、填补缺失档位、对挂单量做异常值截断。我自己的流水线里还会记录每个清洗动作的次数,作为数据质量监控指标——如果某天异常比例突然升高,大概率是上游数据源出问题了。
1.3 多品种订单簿的横截面拼接问题
如果你想做多品种联合建模,比单品种复杂得多。核心问题在于不同标的的订单簿深度、价格水平、交易时段差异很大,直接把不同品种的订单簿特征拼成一个状态向量喂给模型,模型会花大量容量去学习标的不相关规律。
我在实际项目里采用的方案是:先对每个品种单独做标准化,再通过一个代理变量(比如中间价的日波动率)将不同品种映射到同一量纲下,最后用“相对深度”(每档挂单量除以过去某个窗口的平均挂单量)替代绝对深度。这样横截面拼接的可行性会大幅提升,模型泛化能力也更好。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么RL能驾驭高频订单簿:传统监督学习的三个盲区
在讨论如何建模之前,先花点篇幅说说为什么基于订单簿的交易问题天然适合RL框架,而传统监督学习在这里有难以逾越的盲区。理解了这一点,你才能在设计模型时做出正确取舍。
2.1 传统模型预测的是“值”,RL优化的是“决策”
传统监督学习的路径是:从订单簿特征到未来价格变动(回归),或者到未来方向(分类),然后根据预测结果再设计一个独立的交易规则来执行买卖。
这里有两个断层。第一,你预测的量和实际关心的量(账户PnL)之间存在复杂非线性关系,比如你预测未来5分钟价格涨0.2%,该下多少仓位?预测对了但仓位下错了,照样亏钱。第二,市场是反馈系统,你下的单会影响后续订单簿状态,传统静态预测模型没有这个闭环概念。
RL则天然是面向决策的:智能体在t时刻观测订单簿状态,输出一个交易动作(买卖数量),环境反馈新的订单簿状态和一个奖励信号(通常是PnL变动),智能体不断调整策略来最大化累积奖励。整个流程只有一个目标——赚钱,而不是中间某个代理指标。
2.2 序列依赖与部分可观测:订单簿本质是POMDP
订单簿数据的核心特征是高度序列相关,当前买卖盘的薄厚很大程度取决于过去一段时间的委托流,这种时间依赖用RNN、Transformer能学,但总有种“硬塞”的别扭感。问题在于,委托流和成交流之间存在隐藏变量,比如大机构的拆单算法、做市商的库存容忍度,这些你从公开订单簿里永远无法完全观测。
RL框架天然支持POMDP建模,你可以通过循环神经网络或注意力机制维护一个隐状态,让模型自己学会从历史观测序列中推断隐藏信息。我在实际试验中发现,在相同特征输入下,带LSTM隐状态的DQN变体比单纯MLP策略网络在收益上高出约30%到50%,而且回撤更低,这其实就是POMDP建模能力带来的差异。
2.3 探索与利用的权衡在交易中的真实含义
交易策略本身就是一个探索与利用的问题:是继续执行当前已验证有效的策略,还是尝试新的策略逻辑?传统回测框架里你只能预设好规则,然后看历史结果,用户放弃了在线探索的可能。而RL里的ε-greedy、熵正则等机制能在训练阶段让模型自主探索更优交易方式。
不过这里需要泼一盆冷水:在线探索在实盘交易里非常危险,探索意味着可能下出不理性的单。我的建议是,利用RL探索能力训练离线策略,实盘部署时把探索率降为零,完全利用已学到的策略,这能兼顾创新和稳定。
3. 从原始订单簿到强化学习环境:特征工程与状态表示的细节
前面聊了理论动机,现在进入实操环节:如何把原始Level 2订单簿转换成RL环境里可用的状态表示。这是整个流程中最繁琐、最影响上限的部分,也是我在反复试错后收获最大的一段。
3.1 五类最有效的订单簿原始特征
我整理了自己项目里用过且证明有效的订单簿特征,归纳为五类:
- 价格类特征:买一卖一中间价、加权中间价、对数中间价变动率、各档位价位与中间价的距离。
- 深度类特征:各档挂单量、总量不平衡(买卖盘总挂单量之差)、价格档位之间的斜率、离散度。
- 流特征:订单簿事件流中各类事件的频次(新增买单/卖单、撤单、成交)、事件量能差异、委托到达强度。
- 交叉类特征:买卖价差的动态变化、平均成交单量与挂单量的比值、大单冲击后价位的恢复速度。
- 微结构特征:OTO(订单到成交比率)、买卖压力差、订单流毒性指标(如VPIN的简化版)。
有一点要特别注意:不要炫技式地把所有特征全部堆进状态向量。特征维度爆炸会让RL训练速度急剧下降,还需要更长的训练步数才能收敛。我最终使用的状态是大约40到60维特征向量,再经过一个归一化和降维步骤,效果最稳定。
3.2 归一化:订单簿特征的标准姿势
很多人直接对原始挂单量做归一化,这是一个常见误解。由于不同时段、不同标的的挂单量变化范围相差很大,用固定最大最小值归一化会导致模型在不同行情风格下表现不稳定,实盘中遇到放量行情容易崩。
我推荐的做法是滚动归一化:取过去N个快照(比如200个)的滑动窗口,计算每个特征的均值和标准差,然后用当前值与均值的差除以标准差。这样可以自适应不同时段的市场活跃度,模型面对突发行情时更加稳健。
时间戳间隔归一化也经常被忽视。订单簿快照不是均匀时间间隔的,有的快照间隔几毫秒,有的几十毫秒甚至几百毫秒,直接拼接成序列会引入虚假规律。我通常在时间维度上做插值重采样,把不规则的快照流转换为固定的100毫秒或500毫秒间隔,这样输入序列的时间语义才统一。
3.3 动作空间设计:离散动作还是连续动作
这是RL订单簿建模中最具争议的设计决策之一。离散动作空间(买入N手、卖出N手、持有)简单直观,动作维度低,训练容易收敛,但粒度粗糙,很难表达复杂的仓位管理策略。
连续动作空间(输出一个在[-1,1]之间的值,表示当前仓位比例)更接近真实交易需求,但训练难度呈几何级数上升,而且对奖励函数的微小扰动极度敏感。
我的实践经验是:如果你刚开始做,先用三档离散动作(卖出固定量、持有、买入固定量),把整个pipeline跑通,之后再根据策略需求逐渐细化。我见过太多人一上来就上连续动作,结果模型发散后在Debug环境里绕了好几个星期还在跟“NaN loss”作斗争。
如果你确实需要连续仓位控制,我更推荐另一条路线:用离散动作作为策略网络输出,再通过一个确定性的仓位映射函数把离散动作映射到连续的仓位目标上。这样既获得连续控制的灵活性,又避免了直接回归连续值的训练难度。
4. 奖励函数设计:理论说起来很爽,落地全是坑
如果说订单簿特征和状态表示决定模型学习的上限,那奖励函数就直接决定模型找到的策略与真实交易目标之间的契合度。这也是我见过最多人“挂在悬崖边”的环节。
4.1 基础奖励:PnL不是最好的奖励信号
最直觉的做法是把每步交易后的账户PnL变化作为奖励。但实际跑起来你会发现两个问题:第一,PnL变化方差极大,RL训练的收敛性和稳定性都很差;第二,市场本身的涨跌会造成被动盈亏,这与动作是否优秀无关,智能体容易错误归因。
我更推荐的方案是“超额收益类奖励”:用订单簿中间价的变动作为市场基准,把组合收益减去市场收益得到的超额部分作为主要奖励。这样模型学到的是交易能力本身,而非顺势盈利的假象。手续费和冲击成本也应在这一环节扣除,否则模型会学到激进换手的危险策略。
4.2 惩罚项设计:流动性、持仓、波动率的现实约束
单纯用PnL类奖励训练出来的策略,往往表现为频繁报单、持仓极端集中、交易量远超实际可成交的量。这在回测里也许盈利率很高,一上实盘就会被手续费和滑点打得鼻青脸肿。
解决方法是引入约束型惩罚项。我常用三类:
- 持仓惩罚:当|当前持仓比例|超过阈值时给予额外负奖励,防止模型过度集中。
- 换手惩罚:对大幅度的动作变化施加惩罚,限制交易的过度敏感,降低手续费冲击。
- 波动率惩罚:若组合瞬时收益率波动过大,施加惩罚。这个对控制策略回撤非常有效。
不过惩罚项的系数需要精心调教。系数太小,约束形同虚设;系数太大,模型会变得趋避过度,策略看起来非常保守,几乎空仓,收益也被压制得太低。我通常以“空仓时奖励期望约为0”作为基准,再通过网格搜索在不同惩罚强度下对比回测结果。
4.3 Reward Hacking:你不想遇到的诡异现象
Reward Hacking是RL训练里最让人头疼的问题:模型学会了“欺骗”奖励函数,而不是真正实现目标。我在订单簿建模中遇到过几种典型场景:
比如,模型发现频繁撤单后重新挂单可以制造“虚假活跃”的订单簿特征,从而在奖励函数的流动性维度上获得高分,但实际上根本没有真实成交,纯属浪费系统资源。再比如,模型学会了大单瞬间砸盘,在自己挂买单前先把盘口砸出深度,然后在奖励函数计算时从中获益,这种策略对实盘流动性冲击巨大,回测却显示收益颇高。
应对Reward Hacking没有银弹,我的经验是:用多个统计指标同时作为奖励组成部分,而不只依赖单一指标;同时保留“审计轨迹”——每次训练结束后,人工抽检模型表现较好的时间段里面下出的订单序列,判断这些动作是否具有合理的交易含义。
5. 模型与训练框架选型:qlib、Gym环境还是自己写
当数据、环境、奖励都准备好了,接下来就是模型结构和训练框架的问题。这个环节的选型直接影响你的开发效率和迭代速度,我尽量客观地讲讲我在这个方向上的对比和实践。
5.1 策略网络结构:CNN、LSTM还是Transformer
订单簿数据在空间上(各档位对比)和时间上(序列演变)都有很强结构,因此主流做法是把订单簿快照整理成二维张量,让模型同时吸收空间和时间信息。
我实测过三种结构的代表方案:
- 纯LSTM:参数少、训练快、对数据量要求低,适合小资金小规模策略。缺点是空间结构特征利用不足。
- CNN+LSTM混合:先用卷积层提取跨档位和跨特征的空间模式,再输入LSTM捕捉时间演化。综合效果不错,是很多论文的标准组合,也是我主力方案。
- Transformer+多头注意力:表达能力强,能捕捉长距离依赖,但对数据量要求极高,训练时间长出一到两个数量级。我用它跑半导体、新能源等波动率大的板块时,效果时好时坏,稳定性较差。
入门的时候我建议直接走“CNN+LSTM”路线,结构简单、效果可控,等你对RL本身理解深刻了再考虑复杂模型。
5.2 训练框架对比:qlib、自定义Gym环境与RL库的选择
近几年微软开源的qlib在量化爱好者中口碑不错,它内置了数据处理、因子分析、模型训练和回测的完整pipeline,尤其对传统监督学习支持完善。但对RL+订单簿这个细分场景,qlib支持相对有限——它更多是“先预测、后交易”的传统范式,强化学习环境并不灵活,自定义状态转移逻辑和奖励计算比较麻烦。
如果一定要用qlib,可以走“qlib处理数据+外部RL训练”的混合路径:用qlib完成数据清洗、拼接、打分,然后把结果作为状态向量传给外部的RL平台训练策略。这个组合省去了很多数据工程时间,同时保留RL建模灵活性。
通用RL库方面,我推荐用Stable-Baselines3作为起步平台,它实现了PPO、SAC、TD3等主流算法,环境接口遵循Gym规范,社区活跃,遇到问题比较容易搜到答案。当算法原型验证完毕后,再针对具体需求决定是否用纯Python重写训练逻辑,以提高分布式训练和低延迟推理的效率。
5.3 回测环境构建中的撮合细节
回测是RL订单簿建模中最容易“失真”的环节。核心原因是,你的RL智能体在模拟环境中做买卖决策时,模拟器里的成交假设必须与实盘尽量一致,否则策略表现完全是假象。
我的回测环境里包含三层撮合假设:
- 第一层:市价单成交价按当前盘口买一/卖一价成交,考虑手续费与固定滑点。
- 第二层:限价单能否成交,取决于后续订单簿中是否有对手单扫到你的价格,这个必须通过撮合模拟器回放真实逐笔委托流来判断,不能简单假设“不成交”。
- 第三层:大单冲击会让后续订单簿状态发生偏移,我的方案是采用“部分成交+冲击成本倍率”来近似。这部分模型误差较大,但总比完全忽略更贴近真实。
6. 回测里的暗礁:未来函数、时间不等距与下单延迟假设
即便你的RL环境、策略模型都建好了,回测结果依然可能严重偏离实盘。我在这部分总结了最常被忽视的三个“回测暗礁”,每一条都值得你反复核对。
6.1 未来函数:多数“高收益策略”的溺水根源
做传统因子时,大家已经很小心避免未来函数,比如用当天的数据去预测当天,这在订单簿场景里更容易踩坑。原因是订单簿快照和对应的价格标签在时间戳上极易错位——快照数据可能是t时刻的,你给它配的奖励却是t+1到t+2时刻已经全部完成的成交回报,相当于模型看到了答案。
我采用的防御手段是:建立严格的“时间戳防火墙”。环境状态更新只允许使用“当前动作执行后经过一定延迟(比如一个tick或固定毫秒数)”之后到达的数据;所有标签的起始时间必须严格晚于状态最新时间戳。回测结束后,我还会随机抽取几天数据,人工检查动作时间与对应标签时间是否有重叠。
6.2 时间不等距对RL样本效率的影响
前面提到过,订单簿快照本身是不等间隔的,这不仅仅是特征问题,它还直接影响RL训练经验池的效率。如果你直接把不等间隔样本丢进经验回放池,模型会偏向“高事件频率时段”(比如开盘瞬间)的数据,导致策略过度适应这些时段而忽略平稳时段。
我常用的一种解法是“时间加权采样”或“事件块采样”:按等时间间隔切分经验池,确保每个时间窗内样本数量大致均匀;如果某一时段样本数过少,则用相邻样本插值或复制增强。经过这样处理后,训练时间和收敛稳定性都有了显著提升。
6.3 下单延迟假设:你以为的成交价永远不是你以为
在回测中,智能体发出买单信号后,你假设它立刻以当前卖一价成交。真实情况是,从模型推理到订单抵达交易所清算,中间有网络延迟、系统延迟、交易所排队延迟,大约会花掉10到50毫秒,甚至更长。这在高频交易场景里造成的滑点可以吞掉大部分收益。
我在构建回测环境时,给所有动作加入一个服从正态分布的随机延迟(均值约20毫秒,标准差约8毫秒),然后根据延迟后的盘口状态重新计算成交价。这个简单改动让我的策略回测收益平均降低了约20%到35%,但换来的是回测与实盘之间显著缩小的偏差。
7. 从回测到实盘:那些仿真环境永远教不了你的经验
很多人以为回测跑得漂亮就是大功告成,实盘连接、数据推送、订单管理这些现实中硬邦邦的问题接踵而至。我在这个阶段交了不少学费,希望下面的经验能让你少走一些弯路。
7.1 数据链路延迟与信号衰变测试
实盘的第一步是确认你的数据链路延迟是否满足策略需求。RL智能体需要对订单簿变化做快速响应,如果你的数据推送延迟在100毫秒以上,那策略决策的有效性会大打折扣,特别是高频交易场景。
我在部署前的标准流程是:连续记录交易系统从行情事件触发到收到完整快照之间的耗时,按天统计P95、P99延迟,再对比策略训练时的假设延迟。如果实际延迟比训练假设大很多,有两种选择:降低策略交易频率(比如从秒级降到分钟级),或者优化数据链路架构(比如改用更高优先级的行情通道、优化解析代码减少CPU开销)。
7.2 交易系统架构:RL模型推理放哪里
实盘系统架构上,最核心的决策是RL模型的推理位置。如果你用Python做模型推理,每笔决策可能耗时几十甚至上百毫秒,对高频交易来说难以接受。业内常用方案是用C++或Rust封装模型推理,Python只负责任务调度。
如果团队规模不大,也可以用折中方案:Python实时进程负责接收行情和决策调度,模型推理通过ONNX Runtime或TensorRT加速,目标是单个决策推理控制在5到10毫秒以内。我在项目里用ONNX导出TensorFlow训练的CNN+LSTM策略,单次推理从约25毫秒优化到约6毫秒,效果差距显著。
7.3 风控模块:RL系统里的“熔断机制”
RL策略天然具有自适应性和复杂性,这意味着它可能在实盘中做出训练阶段从未出现过的动作。因此风控模块不能省,而且必须独立于RL策略之外强制性拦截。
我在订单管理器和策略之间加了一层硬性风控规则,优先级最高,包括价格限制监控(偏离中间价超过一定百分比直接拒绝下单)、持仓限制(每日单标的最大仓位不超过总资金比例)、连续亏损熔断(当日策略PnL回撤超过阈值则暂停信号输出,转入手动诊断)和频率限制(每秒最多下单次数,防止模型陷入reward hacking的疯狂循环)。
8. 我从这些失败里总结出的经验清单
项目走到今天,我踩过的坑、推倒重来的模型、上线后紧急撤回的策略不在少数。每次事故复盘时我都会更新一份经验清单,可能对你有直接参考价值。
8.1 数据与特征层面的教训
- 先用两到三个月的历史数据反复清洗和可视化分析,确认订单簿数据的行为规律(比如不同交易时段盘口深度的分布差异)后再开始建模,很多问题在数据探索阶段就能暴露。
- 特征设计宁精勿多。40到60维经过归一化的特征已经足够让大多数RL模型学到有效策略;特征维度过高反而会拖慢训练速度,增加过拟合方差。
- 每一种新特征加入前,先在传统监督模型上做一个单特征有效性测试。如果这个特征连静态模型都无法提升效果,不要期望RL能创造奇迹。
8.2 训练与评估层面的教训
- 评估RL策略时,必须使用多个不同市场周期(趋势市、震荡市、高波动期、低波动期)组成的测试集。单一周期回测表现好没有任何意义——我见过太多策略在低波动期里收益漂亮得像天文数字,一到高波动市直接原形毕露。
- 每个模型配置至少跑五次不同随机种子训练,只看一次结果做判断等于自欺欺人。平均分、标准差都要记录,模型好坏不仅要看上限,还要看下限。
- 定期用传统策略(比如简单的MACD趋势策略、布林带回归策略)作为基准线,用来对比RL策略的相对优势。如果RL策略连简单规则策略都跑不赢,那问题大概率出在环境设计或奖励塑形,而不是模型结构。
8.3 工程与部署层面的教训
- 尽早设计“策略下线开关”。当实盘表现与回测预期偏差过大时,一键止损比任何精妙的技术修复都重要。
- 模型回测所用的延迟假设、手续费参数、滑点模型,全部参数必须整合在配置文件中,并且打上版本号。这样一处更新,回测和实盘同步修改,避免两边信息不一致导致的伪回归现象。
- 日志系统必须记录每次交易决策所依据的状态快照、状态特征向量和推理结果为后续归因分析做准备。没有这些数据的策略系统就像没有黑匣子的飞机,出问题后很难追溯。
最后再分享一个我做RL订单簿项目时的体会:很多人以为最难点在模型结构或者算法创新,实际上真正决定项目生死的是数据可靠性、环境真实度和奖励函数合理性。模型本身反而在最后20%的努力里。如果你正在规划这个方向的项目,我的建议是花至少40%的时间在数据处理和环境搭建上,这个时间绝对不白费。另外从仿真到实盘之间,先找一个流动性适中的标的做试点,不要一上来就用自己的主力资金验证策略逻辑。这个领域没有捷径,但也不像数学物理那样需要你去“发明”——现有工程方法已经足够让你从零搭建一套可用的系统,关键是把每个环节踩扎实。
