3、订单簿微观结构分析:订单簿重建、买卖压力指标、订单流不平衡检测、高频交易特征提取
好,咱们进入第三章。这一章我打算聊聊订单簿的微观结构。
说实话,做跨市场监控这么多年,我最大的体会就是:价格是结果,订单簿才是原因。你盯着K线看,看到的都是过去;你盯着订单簿看,看到的才是当下正在发生的事。尤其在高频场景下,订单簿的每一笔变动,都可能藏着大资金的真实意图。
3.1 订单簿重建:从原始数据到实时快照
先说说订单簿重建。交易所给的数据,通常是逐笔委托和逐笔成交。你得自己把它们拼成完整的订单簿。
我个人习惯用增量更新的方式。什么意思呢?就是维护一个本地的订单簿快照,然后每来一条新委托,就做对应的增删改操作。
核心逻辑:
- 新增委托:按价格插入到对应的买盘或卖盘队列
- 撤单:从队列中移除指定OrderID
- 成交:减少对应委托的数量,如果数量归零则移除
这里有个坑,我曾经踩过。有些交易所的行情数据会有乱序,比如撤单消息比委托消息先到。你想想看,本地订单簿里还没这条委托呢,撤单指令来了,怎么办?
避坑指南:我曾经因为没处理乱序,导致订单簿深度计算偏差了整整一个下午。后来我加了一个延迟队列,把时间戳相差50ms以内的消息先缓存,排序后再处理。效果立竿见影。
代码实现上,我一般用collections.OrderedDict来维护每个价位的委托队列。为什么?因为Python的dict在3.7之后就是有序的,插入和删除都是O(1),非常适合高频场景。
class OrderBook:
def __init__(self):
self.bids = {} # price -> OrderedDict(order_id -> volume)
self.asks = {}
def apply_snapshot(self, snapshot):
# 重建整个订单簿
self.bids.clear()
self.asks.clear()
for level in snapshot['bids']:
self.bids[level['price']] = OrderedDict()
for order in level['orders']:
self.bids[level['price']][order['id']] = order['qty']
# asks同理
def apply_update(self, update):
side = self.bids if update['side'] == 'buy' else self.asks
if update['type'] == 'new':
if update['price'] not in side:
side[update['price']] = OrderedDict()
side[update['price']][update['order_id']] = update['qty']
elif update['type'] == 'cancel':
if update['price'] in side:
side[update['price']].pop(update['order_id'], None)
if not side[update['price']]:
del side[update['price']]
# ... 成交处理类似
3.2 买卖压力指标:谁在主导市场?
订单簿重建好了,接下来就是算指标。我最常用的一个指标叫买卖压力比(Bid-Ask Pressure Ratio)。
说白了,就是看买盘的总深度和卖盘的总深度谁更大。但这里有个细节:不能只看第一档。大资金往往会藏在后面几档,慢慢吸筹或出货。
| 指标名称 | 计算公式 | 含义 |
|---|---|---|
| 买卖压力比 | ∑(买盘量) / ∑(卖盘量) | >1 表示买盘占优,<1 表示卖盘占优 |
| 加权压力比 | ∑(买盘量/价差) / ∑(卖盘量/价差) | 考虑距离最优价的远近,越近权重越高 |
| 深度不平衡 | (买盘深度 - 卖盘深度) / (买盘深度 + 卖盘深度) | 归一化到[-1, 1],便于跨品种比较 |
我在项目中遇到过一种情况:某只股票价格一直横盘,但买卖压力比持续大于1.2。我当时就觉得不对劲,后来发现是有机构在偷偷吸筹。果然,三天后一根大阳线拉起来了。
小技巧:计算压力比时,建议用对数变换。因为原始比值可能从0.1到10跨度太大,取log之后更平滑,也更容易设定阈值。
3.3 订单流不平衡检测:捕捉微观动量
订单流不平衡,比买卖压力更微观。它关注的是每一笔成交的主动方向。
怎么判断主动方向?很简单:如果一笔成交发生在卖一价,那就是主动买入;发生在买一价,就是主动卖出。这就是经典的Tick规则。
但这里有个问题。当价格不变时,你怎么判断?比如连续两笔成交都在同一个价位。嗯,这时候就要用报价规则了:看成交前的最后一笔报价变动方向。
def classify_trade(trade, prev_trade, best_bid, best_ask):
# Tick规则
if trade['price'] > prev_trade['price']:
return 'buy'
elif trade['price'] < prev_trade['price']:
return 'sell'
else:
# 报价规则:价格不变时,看成交价相对于最优报价的位置
if trade['price'] == best_ask:
return 'buy'
elif trade['price'] == best_bid:
return 'sell'
else:
return 'neutral'
有了每笔成交的方向,就可以算订单流不平衡了。我常用的一个指标是累计订单流不平衡(Cumulative Order Flow Imbalance, COFI)。
公式很简单:COFI = Σ(主动买入量 - 主动卖出量),在一个时间窗口内累加。
为什么会关注这个?因为COFI和未来短期的价格变化有很强的相关性。我记得有一次做回测,COFI的10秒变化量和未来5秒的收益率相关系数达到了0.6以上。这在金融数据里已经算很高了。
注意:COFI在高频环境下会非常嘈杂。我建议先做指数加权平滑,或者用分位数截断去掉极端值。否则噪声会淹没信号。
3.4 高频交易特征提取:从微观到宏观
最后,咱们聊聊怎么从订单簿数据里提取有用的特征。这些特征最终会喂给模型,用来预测价格变动或检测异常。
我个人把特征分成三类:
- 静态特征:买卖价差、深度、订单簿斜率等,反映当前市场状态
- 动态特征:订单到达率、撤单率、成交率,反映市场活跃度
- 交互特征:订单流不平衡与价格变化的交叉项,反映因果关系
举个例子,订单簿斜率怎么算?其实就是对订单簿的深度曲线做线性回归,斜率越大,说明深度越集中在最优价附近,市场流动性越好。
def order_book_slope(book, n_levels=10):
# 取前n档的累计深度
bid_prices = sorted(book.bids.keys(), reverse=True)[:n_levels]
ask_prices = sorted(book.asks.keys())[:n_levels]
bid_depth = [sum(book.bids[p].values()) for p in bid_prices]
ask_depth = [sum(book.asks[p].values()) for p in ask_prices]
# 用最小二乘法拟合斜率
x = np.arange(n_levels)
slope_bid = np.polyfit(x, bid_depth, 1)[0]
slope_ask = np.polyfit(x, ask_depth, 1)[0]
return slope_bid, slope_ask
还有一个我特别喜欢的特征:订单簿熵。它衡量的是订单在不同价位上的分布均匀程度。熵值高,说明订单分散,市场分歧大;熵值低,说明订单集中,市场共识强。
实战经验:我在做跨市场套利监控时,发现当两个市场的订单簿熵值同时快速下降时,往往预示着一次大的价格波动即将来临。这个信号比单纯的价差变化要早几十毫秒。
好了,这一章的内容就这些。订单簿微观结构分析,说白了就是从数据中还原市场参与者的真实行为。你掌握了这些指标,就能比大多数人更早看到市场的真实动向。
公众号:蓝海资料掘金营,微信deep3321