第四章:核心算法一:TWAP(时间加权平均价格)算法

各位同学,今天我们来聊聊TWAP。这个算法在量化交易圈子里,可以说是最基础、最经典的大单拆解策略之一。我刚开始做算法交易那会儿,第一个上生产环境的策略就是TWAP,所以对它感情挺深的。

TWAP的全称是Time-Weighted Average Price,时间加权平均价格。说白了,就是把一个大订单,按照时间均匀地拆成小单子,然后分批执行。目标很简单——让我们的成交均价,尽量接近这段时间的市场平均价。

核心思想:时间均匀,价格跟随。不预测市场,只做时间的朋友。

一、数学公式:其实很简单

TWAP的数学表达,我个人觉得是算法交易里最直观的。你想想看,我们要把总量为Q的订单,在T个时间切片内执行完。每个切片分配的数量就是:

q = Q / T

其中:

  • Q:总订单数量
  • T:总时间切片数(比如240个1分钟切片)
  • q:每个切片要执行的量

那最终的TWAP价格怎么算?其实就是每个切片成交价格的加权平均,权重就是每个切片成交的数量:

TWAP = Σ(P_i × q_i) / Σ(q_i)

这里P_i是第i个切片的成交价格,q_i是第i个切片实际成交的数量。如果每个切片都完全成交,那TWAP就等于所有切片价格的算术平均。

我的经验:实际生产中,q_i往往不等于q。因为市场深度不够,或者价格滑点太大,我们可能只成交了部分。这时候TWAP的计算就要用实际成交数据,而不是理论分配量。我曾经踩过这个坑,回测时用理论值算,上线后发现对不上账。

二、适用场景:低流动性、非紧急订单

TWAP最适合什么场景?我总结了两类:

  1. 低流动性市场:比如某些小盘股、债券、或者流动性差的期货合约。你一个大单砸进去,直接就把价格打飞了。TWAP帮你把单子拆碎,慢慢喂给市场。
  2. 非紧急订单:你不急着在几分钟内完成交易。比如你是做指数增强的,今天要调仓,但可以慢慢来,花一整天甚至几天完成。

我记得有一次做港股交易,某只小盘股一天成交量才几百万港币。客户要买两百万,如果用市价单直接冲,估计能把股价拉高5%。后来我们用TWAP,从早上9:30一直拆到下午3:55,最后成交均价只比开盘价高了0.3%。客户很满意。

注意:TWAP不适合做「抢单」或者「追涨杀跌」。它本质上是一个被动策略,不预测价格方向。如果你觉得市场马上要大涨,想赶紧买入,那TWAP会让你错过行情。

三、局限性:TWAP不是万能的

任何算法都有短板,TWAP也不例外。我把它的问题归纳为三点:

局限性 具体表现 我的建议
无视市场微观结构 TWAP只关心时间,不关心订单簿的深度、买卖盘口的变化。如果某个时刻买单特别少,它还是会硬塞进去,导致滑点。 可以结合VWAP或者加入流动性检测逻辑
容易被对手盘识别 因为每个时间切片都均匀下单,对手盘如果监测到你的规律,可以在你下单前挂单等你。 加入随机化,比如每个切片的时间点偏移几秒
无法应对极端行情 如果市场突然暴跌,TWAP不会暂停或调整,依然按节奏下单。这可能导致你在下跌过程中不断买入。 设置价格保护阈值,超过一定幅度就暂停

嗯,这里要特别说一下第一条。我曾经在回测时发现TWAP表现很好,但实盘时滑点比预期大了一倍。后来排查发现,是因为回测时假设每个切片都能按中间价成交,但实盘时某些切片正好遇到买单稀薄,导致成交价被拉高。从那以后,我所有TWAP策略都会加上一个「流动性过滤器」——如果当前盘口的买单深度不够,就延迟下单。

四、代码实现:一个简单的TWAP引擎

下面我给一个Python实现。这个版本我做了简化,但核心逻辑都在。你可以在自己的回测框架里直接套用。

import time
import random
from datetime import datetime, timedelta

class TWAPEngine:
    """
    一个简单的TWAP执行引擎
    """
    def __init__(self, total_qty, total_seconds, slices=240):
        self.total_qty = total_qty          # 总订单量
        self.total_seconds = total_seconds  # 总执行时间(秒)
        self.slices = slices                # 切片数
        self.slice_qty = total_qty / slices # 每个切片的理论量
        self.slice_interval = total_seconds / slices  # 每个切片的间隔
        
    def generate_schedule(self):
        """
        生成执行计划
        """
        schedule = []
        for i in range(self.slices):
            # 这里可以加入随机偏移,避免被识别
            delay = random.uniform(-0.5, 0.5)  # 偏移±0.5秒
            exec_time = i * self.slice_interval + delay
            schedule.append({
                'time': exec_time,
                'qty': self.slice_qty
            })
        return schedule
    
    def execute(self, market_api):
        """
        执行TWAP策略
        market_api: 模拟的市场接口,需要实现 place_order() 方法
        """
        schedule = self.generate_schedule()
        executed_qty = 0
        total_cost = 0
        
        for task in schedule:
            # 等待到指定时间
            time.sleep(task['time'] - (datetime.now() - start_time).total_seconds())
            
            # 检查市场状态(简化版)
            if market_api.is_market_open():
                # 下单
                order = market_api.place_order(
                    qty=task['qty'],
                    order_type='limit',
                    price=market_api.get_current_price()
                )
                # 记录成交
                executed_qty += order['filled_qty']
                total_cost += order['filled_qty'] * order['avg_price']
        
        # 计算TWAP
        if executed_qty > 0:
            twap_price = total_cost / executed_qty
        else:
            twap_price = 0
            
        return {
            'executed_qty': executed_qty,
            'twap_price': twap_price,
            'total_cost': total_cost
        }

# 使用示例
if __name__ == '__main__':
    engine = TWAPEngine(total_qty=100000, total_seconds=3600, slices=60)
    # 假设 market_api 已经初始化
    # result = engine.execute(market_api)
    # print(f"成交数量: {result['executed_qty']}, TWAP价格: {result['twap_price']}")

避坑指南:我曾经在生成执行计划时,忘记考虑交易所的撮合延迟。结果每个切片的下单时间都比预期晚了0.1-0.2秒,导致最后几个切片没来得及执行。后来我在每个切片之间加了「时间缓冲」,确保即使有延迟也能完成。

五、知识体系图

下面我用一张SVG图,把TWAP的核心逻辑串起来。你可以把它当作一个快速参考。

TWAP算法知识体系 输入:大订单Q 核心逻辑:时间均匀拆分 q = Q / T(每个切片等量) 执行过程:按时间切片逐批下单 可加入随机偏移、流动性检测等优化 输出:TWAP价格 适用:低流动性、非紧急 局限:无视微观结构、易被识别

这张图把TWAP的输入、核心逻辑、执行过程、输出,以及适用场景和局限性都串起来了。你可以把它打印出来贴在工位上,写代码时瞄一眼,思路会清晰很多。


好了,TWAP就讲到这里。说白了,它是一个「笨但可靠」的算法。在大多数常规场景下,它都能很好地完成任务。但如果你遇到的是高流动性、或者需要抢单的场景,那就要考虑下一章要讲的VWAP或者更激进的策略了。

记住一点:没有最好的算法,只有最合适的算法。TWAP是你的工具箱里最基础的那把扳手——不一定最漂亮,但一定最常用。