拆单算法核心原理:时间加权平均价格(TWAP)算法详解

TWAP,全称 Time-Weighted Average Price,时间加权平均价格。这名字听着挺唬人,说白了就是——把一个大单子,切成很多小单子,然后均匀地撒到一段时间里

我刚开始做量化交易那会儿,第一次接触 TWAP 就觉得:这不就是个定时器加循环吗?后来真上了生产环境才发现,坑比想象的多得多。今天咱们就把这个算法掰开揉碎了讲清楚。

TWAP 的核心思想

TWAP 的目标很简单:让成交价格尽可能接近这段时间内的平均价格

你想想看,如果你要买 100 万股某只股票,一次性挂单,市场直接给你拉涨停。但如果你分成 100 次,每次 1 万股,每隔 1 分钟发一次,市场根本感觉不到你的存在。

这就是 TWAP 的哲学:用时间换流动性,用分散换隐蔽

数学公式:其实没那么复杂

TWAP 的计算公式长这样:

TWAP = (P₁ + P₂ + P₃ + ... + Pₙ) / N

其中 P₁ 到 Pₙ 是每个时间点的成交价格,N 是时间点的数量。

嗯,就这么简单。但实际执行的时候,我们要解决的是:如何让我的成交价格,尽可能接近这个理论值

标准 TWAP 算法流程

我一般把 TWAP 拆成三个步骤:

  1. 切分阶段:把总订单量按时间均匀切分
  2. 执行阶段:在每个时间切片内,把对应的子单发出去
  3. 调整阶段:根据市场情况微调剩余订单的节奏

来看一个最基础的实现:

class TWAPExecutor:
    def __init__(self, total_quantity, start_time, end_time, slices=20):
        self.total_qty = total_quantity
        self.start = start_time
        self.end = end_time
        self.slices = slices
        
        # 每片的时间间隔(秒)
        self.interval = (end_time - start_time) / slices
        # 每片的数量
        self.slice_qty = total_quantity / slices
        
    def generate_schedule(self):
        schedule = []
        for i in range(self.slices):
            slice_time = self.start + i * self.interval
            schedule.append({
                'time': slice_time,
                'quantity': self.slice_qty
            })
        return schedule
    
    def execute(self, market_api):
        schedule = self.generate_schedule()
        for slice_info in schedule:
            # 等待到指定时间
            wait_until(slice_info['time'])
            # 发送子单
            market_api.send_order(
                quantity=slice_info['quantity'],
                order_type='LIMIT',
                price=get_current_price()
            )

关键点:这个代码只是教学演示。生产环境里,你还需要考虑滑点、延迟、订单簿深度等因素。

TWAP 的变体:不是所有场景都适合硬切

我在项目中遇到过一个问题:市场突然剧烈波动,但 TWAP 还在按部就班地发单。结果就是,明明可以等一等再买,却买在了高点。

所以后来我做了几个改进:

变体名称 核心改进 适用场景
Volume-Weighted TWAP 根据历史成交量分布调整切片大小 开盘/收盘时段流动性不均
Adaptive TWAP 根据实时市场波动动态调整切片间隔 高波动行情
Residual TWAP 根据已成交情况重新计算剩余切片 部分成交或撤单频繁的场景

说白了,标准的 TWAP 是个「死」算法,而实战中我们需要「活」的 TWAP

避坑指南:我踩过的几个坑

我曾经犯过一个低级错误:把切片数量设得太多,比如 1000 片。结果每片只有几百股,频繁发单导致交易所把我当成了高频交易者,直接限制了接口权限。

这里有几个经验:

  • 切片数量不要超过 50,除非你有特殊需求
  • 每片数量不要低于最小交易单位,否则会报错
  • 留出缓冲时间,最后一片不要卡在结束时间点上

TWAP 与暗池的配合

TWAP 本身不依赖暗池,但两者结合效果更好。我习惯的做法是:

  1. 用 TWAP 生成时间切片
  2. 每个切片优先发到暗池
  3. 如果暗池没有成交,再发到公开市场

这样做的好处是:暗池吃掉了一部分流动性,公开市场的冲击就小了

性能考量:别让算法拖慢系统

TWAP 算法本身计算量很小,但执行时的延迟很关键。我建议:

  • 预计算时间表:在算法启动前就把所有切片算好
  • 使用异步 I/O:不要阻塞主线程等待成交回报
  • 本地时钟同步:确保你的服务器时间和交易所时间一致

一个小技巧:我习惯在算法启动前先发一个测试单,检查网络延迟。如果延迟超过 50ms,我会调整切片间隔,把延迟考虑进去。

TWAP 的局限性

说实话,TWAP 不是万能的。它有几个硬伤:

  • 不感知市场深度:它不管当前买一卖一有多少,只管按时间发单
  • 不预测价格走势:如果价格单边上涨,TWAP 会越买越贵
  • 对极端行情无效:熔断、停牌等情况,TWAP 直接失效

所以,TWAP 适合的是「正常市场、中等规模、不着急」的场景。如果你要抢涨停板,或者要砸盘出货,TWAP 不是好选择。

SVG 流程图:TWAP 执行流程

TWAP 算法执行流程图 接收订单 切分阶段 按时间均匀切分总订单量 执行阶段 按时间表发送子单到市场 调整阶段 根据成交情况微调剩余订单 订单完成 注意事项 • 切片数 ≤ 50 • 每片 ≥ 最小交易单位 • 预留缓冲时间 • 优先走暗池 • 监控网络延迟 • 异步处理成交回报 • 本地时钟同步 • 异常情况熔断 • 记录完整日志 • 回测验证参数

总结一下

TWAP 是拆单算法里最基础、最常用的一种。它不复杂,但要做好不容易。我个人的建议是:

  • 先从标准 TWAP 开始,跑通流程
  • 然后加 Adaptive 逻辑,应对市场变化
  • 最后对接暗池,降低冲击成本

嗯,今天就聊到这儿。TWAP 虽然基础,但它是理解更复杂算法(比如 VWAP、Implementation Shortfall)的基石。把这个吃透了,后面的路就好走了。


专注资料整理