16. 算法交易与深度:TWAP/VWAP对深度的影响、冰山订单、暗池
聊到算法交易对市场深度的影响,我得先坦白一件事——我入行头两年,一直觉得TWAP和VWAP就是个下单工具,跟市场深度没啥关系。直到有一次,我写了个TWAP策略跑在流动性极差的合约上,结果直接把买一价推高了三个tick。嗯,从那以后我才真正开始琢磨:算法交易到底是怎么“吃掉”深度的?
16.1 TWAP与VWAP:时间切片 vs 成交量切片
先说说这两个最基础的算法。TWAP(Time-Weighted Average Price)和VWAP(Volume-Weighted Average Price),说白了都是把大单拆成小单。但拆法不一样,对市场深度的影响也完全不同。
| 算法 | 切片逻辑 | 对深度的影响 |
|---|---|---|
| TWAP | 按时间均匀切片 | 在低流动性时段容易造成深度塌陷 |
| VWAP | 按历史成交量分布切片 | 相对平滑,但遇到异常放量时也会冲击 |
我个人习惯用VWAP多一些。为什么?因为TWAP在午盘休市前那段时间,经常把单子砸在深度最薄的时候。你想想看,市场本来就没多少人交易,你一个算法还在那机械地每秒发单,这不就是往平静的湖面扔石头吗?
核心观点:TWAP适合流动性充足的市场,VWAP更适合流动性分布不均的市场。选错了,你的订单就是在“硬吃”深度。
16.2 冰山订单:藏在水面下的深度
冰山订单(Iceberg Order)是我个人非常喜欢的一个工具。它的逻辑很简单:只暴露订单总量的一小部分,剩下的藏在“水下”。
举个例子,你想买10万手,但只显示2000手在盘口上。每次这2000手被吃掉,系统自动再挂出2000手。直到10万手全部成交。
我在项目中遇到过这样一个场景:某个做市商在ETH永续合约上挂了5000手的买单,但只显示了500手。其他交易者看到买一只有500手,觉得压力不大,就继续往下砸。结果每次砸穿500手,又冒出500手。连续吃了十几次才发现不对劲——这根本就是个无底洞。
避坑指南:我曾经犯过一个错误——把冰山订单的显示量设得太小。比如总订单100万,显示量只有100手。结果因为每次刷新太频繁,被交易所的反作弊系统判定为异常交易,直接限制了API权限。建议显示量至少占总量的1%-5%。
冰山订单对市场深度的影响是双面的:
- 正面:隐藏真实意图,避免价格被推离有利位置
- 负面:反复刷新订单会增加系统负载,也可能被高频交易者“钓”出来
16.3 暗池:看不见的深度
暗池(Dark Pool)这东西,说白了就是一个不公开订单簿的交易场所。你在暗池里挂单,别人看不到你的价格和数量。只有匹配成功时,成交信息才会被公布。
我记得2018年的时候,有个客户问我:“暗池是不是就是黑市?” 我笑了,其实不是。暗池是合法的,主要用来处理大额订单,避免对公开市场造成冲击。
暗池对市场深度的影响很有意思:
- 它把一部分流动性从公开市场“吸走”了
- 公开市场的深度变薄,价格更容易被小单推动
- 但暗池内部的深度很厚,大单可以在里面悄悄成交
注意:暗池不是万能的。我见过有人把暗池当成“免冲击”神器,结果在暗池里挂了1个亿的卖单,等了三天都没成交。为什么?因为暗池里没有对手盘。暗池的深度是“被动深度”,需要有人主动来吃你的单子。
16.4 算法交易如何“吃掉”深度
咱们来画一张图,看看算法交易到底是怎么影响市场深度的。
从这张图你能看出来,不同的算法对深度的影响路径是不一样的。TWAP和VWAP是“主动吃深度”,冰山和暗池是“被动保护深度”。
16.5 实战中的选择策略
说了这么多理论,咱们来点实际的。我一般会根据以下条件来选择算法:
- 订单规模 < 平均深度10%:直接用市价单,别折腾算法了
- 订单规模在10%-50%之间:用VWAP,配合冰山订单隐藏部分意图
- 订单规模 > 50%:必须用暗池+冰山组合,否则你会把盘口打穿
一个真实案例:去年我在做BTC期现套利时,需要同时开多和开空各2000张合约。如果直接挂单,盘口深度瞬间被吃掉30%。我用了VWAP+冰山组合,把订单拆成每单50张,显示量设成10张。结果整个建仓过程持续了45分钟,价格只滑动了0.2个tick。嗯,这就是算法交易对深度保护的威力。
16.6 代码示例:一个简单的冰山订单实现
最后,给你看一个我常用的冰山订单伪代码。别直接复制去生产环境用,但思路可以参考:
class IcebergOrder:
def __init__(self, total_qty, display_qty, price, side):
self.total_qty = total_qty # 总数量
self.display_qty = display_qty # 显示数量
self.remaining = total_qty # 剩余数量
self.price = price
self.side = side # 'buy' 或 'sell'
def place_initial_order(self):
# 首次挂单,只显示 display_qty
qty = min(self.display_qty, self.remaining)
self._send_order(qty, self.price)
self.remaining -= qty
def on_order_filled(self, fill_qty):
# 订单被部分成交后,补单
if self.remaining > 0:
qty = min(self.display_qty, self.remaining)
self._send_order(qty, self.price)
self.remaining -= qty
def _send_order(self, qty, price):
# 实际发送订单到交易所
print(f"发送 {self.side} 订单: {qty} @ {price}")
这个实现有个坑——如果市场快速波动,你的冰山订单可能会被反复吃掉,导致补单频率过高。我建议加一个最小补单间隔,比如500毫秒内不重复补单。
小技巧:冰山订单的显示量不要设成固定值。我习惯用动态显示量——当市场波动率大时,显示量设小一点;波动率小时,显示量可以适当放大。这样既能隐藏意图,又不会因为频繁补单而被系统限制。
好了,关于算法交易与市场深度的关系,今天就聊到这儿。记住一句话:算法交易不是万能的,但不懂算法交易,你在深度面前就是裸奔。