27、订单簿与机器学习:强化学习做市、深度神经网络预测
说实话,走到这一章,才算真正摸到了现代量化交易的核心。前面我们聊了那么多订单簿的微观结构、价格演变的数学规律,但怎么把这些东西用起来?怎么让机器自己学会做市、学会预测?这就是今天要聊的——机器学习 + 订单簿。
我个人习惯把这个问题拆成两个方向:强化学习做市和深度神经网络预测。前者解决的是「怎么行动」,后者解决的是「怎么判断」。你想想看,一个交易系统如果连下一秒的价格方向都判断不准,那做市策略再牛也是白搭。反过来,如果预测准了但不知道怎么执行,那也赚不到钱。所以这两者其实是互补的。
强化学习做市:让机器自己学会赚钱
先聊强化学习做市。嗯,这里要注意,强化学习跟传统的监督学习完全不同。监督学习是你给机器一堆「输入-输出」对,让它学映射关系。但强化学习呢?它没有标准答案,只有奖励信号。说白了,就是让机器自己试错,做对了给糖吃,做错了打手心。
我在项目中遇到过这样一个场景:我们想做一个BTC永续合约的做市机器人。传统做法是写死规则——比如价差设0.1%,挂单量设5个BTC,每隔2秒撤单重挂。但问题是市场状态一直在变,波动率大的时候0.1%的价差可能瞬间被吃掉,波动率小的时候又赚不到钱。后来我们改用强化学习,让机器自己去学什么情况下该收窄价差、什么情况下该放宽。
强化学习做市的核心框架是这样的:
状态空间(State):当前订单簿的快照,包括买卖各10档的价格和数量、当前持仓、市场波动率等。
动作空间(Action):决定挂单的价格偏移量、挂单数量、是否撤单等。
奖励函数(Reward):通常用PnL(盈亏)加上库存惩罚。库存惩罚很重要——如果你持有了太多头寸,一旦价格反向波动就会亏大钱。
我曾经踩过一个坑:一开始我们把奖励函数直接设成「赚到的钱」,结果机器学会了疯狂吃单,持仓量变得巨大,一次黑天鹅事件就爆仓了。后来我们加了一个库存惩罚项,让机器在赚钱的同时尽量保持库存中性。这才稳定下来。
下面是一个简化版的强化学习做市伪代码:
class MarketMakingEnv:
def __init__(self, order_book):
self.order_book = order_book
self.inventory = 0
self.cash = 0
def step(self, action):
# action: [bid_offset, ask_offset, bid_size, ask_size]
bid_price = self.mid_price - action[0]
ask_price = self.mid_price + action[1]
# 模拟成交
if market_buy_order <= ask_price:
self.inventory -= action[3]
self.cash += ask_price * action[3]
if market_sell_order >= bid_price:
self.inventory += action[2]
self.cash -= bid_price * action[2]
# 计算奖励
pnl = self.cash + self.inventory * self.mid_price
inventory_penalty = -0.01 * (self.inventory ** 2)
reward = pnl + inventory_penalty
return next_state, reward, done
你看,这个环境模拟了做市商跟市场对手方交互的过程。强化学习智能体(Agent)通过不断试错,学会在什么情况下挂什么单。我建议刚开始做的时候,先用历史数据做回测,别直接上实盘。因为强化学习在训练初期会疯狂亏钱,实盘扛不住。
深度神经网络预测:从订单簿到价格方向
接下来聊深度神经网络预测。这个方向更成熟一些,说白了就是用神经网络从订单簿数据里提取特征,然后预测未来价格走势。
为什么用深度学习?因为订单簿数据太复杂了。你想想看,一个完整的订单簿快照包含买卖各10档的价格和数量,这就是40个特征。再加上时间维度,每秒钟可能有几十次更新。传统机器学习方法(比如随机森林、XGBoost)很难处理这种高维时序数据。而深度学习,尤其是CNN和LSTM,天生就是干这个的。
我个人习惯用CNN+LSTM混合模型。为什么?因为订单簿数据既有空间结构(不同档位之间的关系),又有时间结构(前后帧之间的变化)。CNN擅长提取空间特征,LSTM擅长捕捉时间依赖。两者结合,效果往往不错。
下面是一个典型的模型结构:
import torch
import torch.nn as nn
class OrderBookCNNLSTM(nn.Module):
def __init__(self):
super().__init__()
# CNN部分:提取订单簿的空间特征
self.conv1 = nn.Conv2d(1, 32, kernel_size=(3, 3))
self.conv2 = nn.Conv2d(32, 64, kernel_size=(3, 3))
self.pool = nn.MaxPool2d(2)
# LSTM部分:捕捉时间依赖
self.lstm = nn.LSTM(input_size=64*5*5, hidden_size=128,
num_layers=2, batch_first=True)
# 全连接层:输出预测
self.fc = nn.Linear(128, 3) # 3分类:涨、跌、平
def forward(self, x):
# x shape: (batch, seq_len, 10, 4)
# 10档买卖,4个特征(价格、数量)
batch_size, seq_len = x.shape[0], x.shape[1]
# CNN处理每一帧
cnn_out = []
for t in range(seq_len):
frame = x[:, t, :, :].unsqueeze(1) # (batch, 1, 10, 4)
frame = self.pool(torch.relu(self.conv1(frame)))
frame = self.pool(torch.relu(self.conv2(frame)))
frame = frame.view(batch_size, -1)
cnn_out.append(frame)
# LSTM处理时序
lstm_in = torch.stack(cnn_out, dim=1) # (batch, seq_len, features)
lstm_out, _ = self.lstm(lstm_in)
# 取最后一个时间步的输出
out = self.fc(lstm_out[:, -1, :])
return out
这个模型输入的是过去N帧的订单簿快照,输出的是未来K个时间步的价格方向(涨、跌、平)。我在项目中试过,用过去50帧(每帧100ms)预测未来5秒的价格方向,准确率大概在65%左右。听起来不高?但你要知道,金融市场本身就有随机性,65%已经能赚钱了。
小技巧:训练的时候别直接用原始价格,要用归一化后的数据。我一般用Z-score归一化,或者用价格相对于移动平均的偏离度。另外,类别不平衡问题很严重——大部分时间价格是「平」的。我建议用加权损失函数,或者对「涨」「跌」类别做上采样。
订单簿特征工程:比模型更重要的事
说到这,我得强调一点:特征工程比模型架构更重要。你想想看,如果你给模型喂的是原始的价格和数量,它很难学到有用的东西。但如果你把数据转换成一些有意义的特征,效果会好很多。
我常用的订单簿特征包括:
| 特征类别 | 具体特征 | 说明 |
|---|---|---|
| 价格特征 | 买卖价差、中间价、加权平均价 | 反映当前价格水平 |
| 深度特征 | 各档位累计深度、深度不平衡度 | 反映市场流动性 |
| 斜率特征 | 订单簿斜率(价格-数量曲线的一阶导) | 反映价格弹性 |
| 微观结构特征 | 订单到达率、撤单率、成交率 | 反映市场活跃度 |
| 时序特征 | 价格变化率、波动率、自相关 | 反映价格动态 |
我曾经做过一个实验:同样的LSTM模型,用原始数据训练和用这些特征训练,预测准确率从55%提升到了68%。你看,特征工程的价值就在这里。
强化学习 vs 深度预测:怎么选?
很多朋友问我:做市到底该用强化学习还是深度预测?我的回答是:看你的目标。
- 如果你想要一个端到端的做市策略,直接输出买卖指令,那就用强化学习。它自己会学会怎么平衡盈利和风险。
- 如果你只是想要一个信号,比如预测未来价格方向,然后用这个信号去指导你的交易规则,那就用深度预测。它更可控,也更容易解释。
我个人更倾向于两者结合:用深度预测模型输出价格方向信号,然后用强化学习模型根据这个信号调整做市参数。这样既利用了深度学习的预测能力,又利用了强化学习的决策能力。
注意:不管是强化学习还是深度预测,都别指望模型能100%赚钱。金融市场是动态博弈的,你的模型在赚钱的同时,别人也在优化他们的模型。我见过太多人把全部身家押在一个模型上,结果市场风格一变就血本无归。记住:永远留一手。
本章知识体系
下面这张图展示了本章的核心逻辑:从订单簿数据出发,经过特征工程,分别进入强化学习做市和深度神经网络预测两条路径,最终形成完整的交易策略。
嗯,这一章的内容就到这里。记住:机器学习不是银弹,它只是工具。真正决定你能否赚钱的,是你对市场的理解、对数据的处理、以及对风险的控制。别迷信模型,多关注业务本身。