第二十三讲:订单簿与算法交易——算法交易对订单簿的影响、反馈循环
说实话,做量化交易这些年,我见过太多人把算法交易想得太简单了。
他们以为算法就是个自动下单的工具,挂上去就完事了。但真正在战场上拼杀过的都知道——算法交易和订单簿之间,存在一个极其微妙的反馈循环。你动一下,市场就跟着动一下;市场动了,你的算法又得跟着调整。说白了,这就是一场动态博弈。
算法交易如何改变订单簿的“生态”
先聊聊最直观的影响。我个人习惯把算法交易对订单簿的作用分成三类:
- 流动性供给型算法——比如做市商算法,它们会在订单簿两侧不断挂单,提供流动性。这类算法会让订单簿变得更“厚”,价差更窄。
- 流动性需求型算法——比如TWAP、VWAP、Iceberg,它们需要吃掉对手盘的单子。这类算法会消耗流动性,导致订单簿变“薄”,甚至出现缺口。
- 套利型算法——比如统计套利、跨交易所套利,它们会快速捕捉价差,导致订单簿上的价格发现速度加快。
我在项目中遇到过最典型的案例:一个做市商团队部署了高频做市算法,结果订单簿上买一卖一的价格几乎每10毫秒就更新一次。肉眼根本看不清,但订单簿的深度分布图却呈现出非常规律的“锯齿状”。
核心观点:算法交易不是简单地“执行订单”,它本身就在重塑订单簿的微观结构。你挂单的方式、速度、数量,都会成为其他算法眼中的“信号”。
反馈循环:算法之间的“军备竞赛”
这里有个很有意思的现象——反馈循环。
你想想看:
- 算法A发现订单簿左侧有大单,于是它决定拆单,用小单慢慢吃。
- 算法B(一个检测冰山订单的算法)发现左侧有异常的小单持续成交,于是它推断出背后有大单,开始抢跑。
- 算法A发现自己的订单总是被抢,于是它改变策略,改用更隐蔽的挂单方式。
- 算法B又更新模型,试图识别新的隐蔽模式……
这就是一个典型的反馈循环。每个算法都在观察订单簿的变化,然后调整自己的行为;调整后的行为又反过来改变订单簿,形成新的信号。
我曾经调试过一个实盘策略,发现回测时表现很好,但一上线就亏钱。排查了三天,最后发现原因:回测时用的是历史订单簿数据,但那些数据里没有包含“其他算法对我的策略做出反应”这个反馈。说白了,回测环境里没有“对手”,但实盘里全是聪明的对手。
避坑指南:千万不要忽略反馈循环。如果你的策略在回测中表现太好,反而要警惕——很可能是因为你没有模拟其他算法对你的策略做出的反应。我曾经吃过这个亏,亏了大概两个月实盘利润才意识到问题。
订单簿信号与算法策略的交互
算法交易员通常会从订单簿中提取哪些信号?我列几个常见的:
| 信号类型 | 描述 | 算法如何利用 |
|---|---|---|
| 订单簿斜率 | 买卖两侧挂单量的对比 | 判断短期方向,调整挂单价格 |
| 价差宽度 | 买一卖一之间的价格差 | 决定是否做市,或者是否使用限价单 |
| 订单到达率 | 单位时间内新订单的数量 | 判断市场活跃度,调整下单频率 |
| 撤销率 | 挂单后又被撤销的比例 | 识别虚假挂单(spoofing) |
| 深度分布 | 不同价位上的挂单量分布 | 寻找支撑/阻力位,优化拆单策略 |
嗯,这里要注意一点:这些信号不是独立的。比如价差变窄,可能意味着做市商算法在竞争,也可能意味着有大资金在偷偷吸筹。你得结合上下文来判断。
一个简单的反馈循环模拟
为了让你更直观地理解,我写了一个极简的Python模拟。它展示了两个算法之间的反馈:
# 极简反馈循环模拟
class OrderBook:
def __init__(self):
self.bids = {100: 1000, 99: 2000} # 价格: 数量
self.asks = {101: 1500, 102: 1800}
def execute_market_buy(self, qty):
# 简单模拟吃掉卖单
for price in sorted(self.asks.keys()):
if qty <= 0:
break
available = self.asks[price]
trade = min(qty, available)
self.asks[price] -= trade
qty -= trade
if self.asks[price] == 0:
del self.asks[price]
return price # 返回成交价格
class AggressiveAlgo:
"""激进型算法:看到大单就抢跑"""
def react(self, ob):
# 如果卖一数量突然减少,认为有大单在吃
if sum(ob.asks.values()) < 2000:
return "抢跑买入"
return "观望"
class DefensiveAlgo:
"""防御型算法:发现被抢跑就改变策略"""
def react(self, ob, was_frontrun):
if was_frontrun:
# 改用冰山订单
return "挂小单,隐藏真实意图"
return "正常挂单"
# 模拟几轮交互
ob = OrderBook()
aggressive = AggressiveAlgo()
defensive = DefensiveAlgo()
for round in range(5):
action1 = aggressive.react(ob)
if action1 == "抢跑买入":
ob.execute_market_buy(500)
print(f"第{round+1}轮:激进算法抢跑,吃掉500股")
action2 = defensive.react(ob, was_frontrun=True)
print(f"第{round+1}轮:防御算法反应 -> {action2}")
else:
print(f"第{round+1}轮:双方观望")
这个例子虽然简单,但核心逻辑是一样的:每个算法都在“观察-决策-行动”,而行动结果又成为下一个观察的输入。这就是反馈循环的本质。
如何设计抗反馈循环的算法?
说实话,没有完美的解决方案。但我个人有几个经验:
- 引入随机性——不要每次都做同样的动作。比如拆单的时间间隔、挂单的价格偏移量,加入一些随机噪声,让其他算法难以预测你。
- 使用冰山订单——隐藏真实意图。我习惯把大单拆成多个小单,每个小单只显示一部分数量。
- 监控自己的“足迹”——实时观察订单簿上是否有异常行为。如果发现自己的订单总是被针对,立即暂停并切换策略。
- 多时间尺度协同——不要只看毫秒级的订单簿变化,也要结合秒级、分钟级的趋势。有时候反馈循环只在极短时间内存在,拉长时间看反而清晰。
一个小技巧:我习惯在算法中加一个“反侦察模块”。它会定期检查:我的订单是否被其他算法“盯上”了?判断方法很简单——如果我的限价单挂上去后,对手盘的价格总是恰好比我低/高一个tick,那基本可以确定有人在针对我。
知识体系结构图
下面这张图总结了本章的核心逻辑:
你看这张图就明白了:算法交易改变订单簿,订单簿产生信号,信号又驱动算法决策。这个循环一旦形成,就会自我强化。如果你设计的算法没有考虑到这个循环,那实盘时大概率会被其他算法“吃掉”。
好了,这一讲就到这里。记住一句话:在算法交易的世界里,你看到的订单簿,其实是所有算法博弈后的“影子”。真正的高手,不是看影子本身,而是通过影子去推断背后的“光源”在哪里。