20、做市策略与算法交易结合:TWAP/VWAP执行、冰山订单、狙击手策略、做市与套利结合
做市策略做到一定深度,你会发现一个尴尬的事实:策略逻辑再漂亮,执行层面一塌糊涂,照样亏钱。我见过太多团队,策略回测曲线美如画,一上实盘就变成心电图——问题往往出在执行上。
今天这章,咱们聊聊做市策略怎么跟算法交易结合。说白了,就是怎么把订单拆得聪明、藏得隐蔽、打得精准。我会从TWAP/VWAP执行讲起,再到冰山订单、狙击手策略,最后聊聊做市和套利怎么配合。
20.1 TWAP/VWAP执行:时间切片与成交量切片
先说说TWAP和VWAP。这两个东西,很多新手以为只是拆单工具,其实它们是做市策略的底层基础设施。
20.1.1 TWAP:时间均匀切片
TWAP(Time-Weighted Average Price)的逻辑很简单:把一个大订单,按照时间均匀地切成小片,每片丢到市场里。我早期做ETH做市时,经常需要调整库存,一次就是几千个ETH。直接砸下去?市场直接给你砸穿。
TWAP的核心参数就两个:总时长和切片数量。比如我要在1小时内卖出1000个ETH,切成60片,每片16.67个ETH,每分钟丢一片。
class TWAPExecutor:
def __init__(self, total_qty, total_seconds, slices=60):
self.total_qty = total_qty
self.slice_qty = total_qty / slices
self.interval = total_seconds / slices
self.slices = slices
self.current_slice = 0
def next_order(self):
if self.current_slice >= self.slices:
return None
self.current_slice += 1
return {
'qty': self.slice_qty,
'time_offset': self.current_slice * self.interval
}
嗯,这里要注意:TWAP有个致命缺陷——它完全不考虑市场成交量。如果市场流动性突然枯竭,你的订单照样往外丢,等于给市场送钱。
20.1.2 VWAP:跟着成交量走
VWAP(Volume-Weighted Average Price)就聪明多了。它根据历史成交量分布来切片。比如某个币种,上午10点成交量占全天的15%,那VWAP就会在那个时段多分配一些订单。
我个人习惯用滚动窗口的VWAP,而不是固定全天的。为什么?因为市场结构会变。我记得2023年有一次,某个山寨币的成交量分布突然变了,固定VWAP直接翻车,滚动窗口反而稳住了。
| 特性 | TWAP | VWAP |
|---|---|---|
| 切片依据 | 时间均匀 | 成交量分布 |
| 市场冲击 | 高(流动性差时) | 低(跟随流动性) |
| 适用场景 | 高流动性品种 | 中等流动性品种 |
| 实现难度 | 低 | 中 |
20.2 冰山订单:藏住你的真实意图
冰山订单(Iceberg Order),说白了就是挂单只露一部分,剩下的藏在水面下。交易所的订单簿上只显示冰山的一角,实际订单量远大于显示的量。
为什么要用冰山订单?你想想看,如果你在订单簿上挂一个1000个ETH的买单,所有人都能看到。做市商看到这么大一个买盘,第一反应就是拉高价格等你来吃。你还没买到,价格已经被拉上去了。
冰山订单的经典结构:
class IcebergOrder:
def __init__(self, total_qty, display_qty, price, min_trigger_qty=0):
self.total_qty = total_qty
self.display_qty = display_qty
self.price = price
self.remaining = total_qty
self.min_trigger_qty = min_trigger_qty
def get_display_order(self):
if self.remaining <= 0:
return None
show = min(self.display_qty, self.remaining)
return {'price': self.price, 'qty': show}
def on_fill(self, filled_qty):
self.remaining -= filled_qty
# 如果剩余量小于最小触发量,不再重新挂单
if self.remaining < self.min_trigger_qty:
return None
# 重新挂出新的冰山一角
return self.get_display_order()
我曾经犯过一个错误:冰山订单的显示量设得太小。比如总订单1000个,每次只显示10个。结果呢?每次刚挂出来就被吃掉,然后重新挂,再被吃。高频交易者一眼就看出来这是冰山,反而暴露了意图。
20.3 狙击手策略:等机会,一击必中
狙击手策略(Sniper Strategy),这个名字很形象——平时不动,看到机会就一枪爆头。在算法交易里,狙击手策略通常用于捕捉流动性缺口或者套利机会。
做市策略里的狙击手,主要干两件事:
- 捕捉错误定价:比如某个做市商报价出现明显错误,价格偏离市场均值超过阈值,狙击手直接吃掉
- 抢跑大订单:检测到有大单正在被吃掉,狙击手提前一步在更优价格挂单
狙击手策略的核心是延迟和概率的平衡。你想想看,机会转瞬即逝,延迟高了就吃不到。但延迟太低,又容易被假信号骗进去。
class SniperStrategy:
def __init__(self, threshold_bps=5, max_latency_ms=10):
self.threshold_bps = threshold_bps # 偏离阈值,单位bps
self.max_latency_ms = max_latency_ms
def evaluate_opportunity(self, orderbook, fair_price):
best_bid = orderbook['bids'][0][0]
best_ask = orderbook['asks'][0][0]
mid_price = (best_bid + best_ask) / 2
# 计算偏离程度
deviation = abs(mid_price - fair_price) / fair_price * 10000
if deviation > self.threshold_bps:
# 发现机会,立即执行
return self.execute_snipe(orderbook, fair_price)
return None
def execute_snipe(self, orderbook, fair_price):
# 这里用冰山订单来隐藏意图
iceberg = IcebergOrder(
total_qty=100,
display_qty=20,
price=fair_price * 0.999 # 略低于公平价格
)
return iceberg.get_display_order()
20.4 做市与套利结合:两条腿走路
做市和套利,听起来是两回事。一个赚买卖价差,一个赚价差回归。但在我眼里,它们是天然互补的。
做市策略最大的风险是什么?库存风险。你不断双边报价,总会有某一方向被大量吃掉,导致库存偏离中性。这时候怎么办?硬扛?还是止损?
我的做法是:用套利策略来对冲库存风险。
举个例子:我在Binance做市,库存里多了100个ETH。如果直接卖掉,会冲击市场。但如果在Bybit上开一个等量的ETH空单,就变成了期现套利。只要两个市场的价差在合理范围内,这个对冲就是免费的。
具体怎么结合?我画了一张图:
这张图的核心逻辑是:做市策略产生库存风险 → 库存监控检测到偏离 → 套利策略执行对冲 → 算法交易负责执行。整个链路闭环,互相配合。
20.4.1 实战中的套利对冲方式
我常用的几种套利对冲方式:
- 跨交易所价差套利:A交易所做市多了100个ETH,B交易所做市少了50个ETH,两边一平衡,库存风险就消掉了
- 期现套利:现货多了,在期货市场开空单。只要资金费率能覆盖成本,这个对冲就是正收益的
- 永续合约对冲:用永续合约的持仓来对冲现货库存。注意资金费率的方向,别被费率吃掉利润
20.5 总结:算法交易是做市策略的放大器
说了这么多,其实就一个意思:做市策略和算法交易不是两个独立的东西,它们是一体的。
TWAP和VWAP帮你控制执行成本,冰山订单帮你隐藏意图,狙击手策略帮你捕捉机会,套利对冲帮你管理风险。把这些工具组合起来,你的做市策略才能真正落地。
我个人最深的体会是:策略的alpha只占30%,执行占70%。很多团队花90%的时间优化定价模型,结果执行层面一塌糊涂。你想想看,定价再好,执行的时候被市场摩擦吃掉,有什么用?
嗯,这章就到这里。下一章我们会聊更实战的东西——如何搭建一个完整的做市系统。到时候见。