第1章:执行算法基础——TWAP、VWAP、POV
各位同学好,我是老张。在量化交易这个行当摸爬滚打了十来年,今天咱们来聊聊执行算法里最基础、也最实用的三个家伙:TWAP、VWAP 和 POV。
你可能会问,为什么一上来就讲这个?
嗯,我见过太多人,策略信号写得漂亮,一到实盘就亏钱。为什么?因为执行太糙。你想想看,一个大单子直接砸下去,市场瞬间就滑出去好几个价位。说白了,策略负责「买什么、卖什么」,执行算法负责「怎么买、怎么卖」。今天这三个,就是执行算法的入门必修课。
一、TWAP:时间加权平均价格
1.1 原理
TWAP 的全称是 Time-Weighted Average Price。它的逻辑很简单:把一个大单子,均匀地拆成小单子,在固定的时间间隔里发出去。
举个例子。你想在 10 分钟内买入 100 万股。TWAP 的做法是:把 10 分钟切成 10 个 1 分钟的小段,每段发 10 万股。不管市场怎么波动,我就按时间切。
为什么会有人用这个?
我个人习惯把它叫做「懒人算法」。当你不确定市场短期走势,又不想暴露大单意图时,TWAP 是最稳妥的选择。它追求的是执行价格尽可能接近这段时间的平均价。
每段下单量 = 总订单量 / 总时间段数
1.2 实现
代码其实不复杂。我直接给一个 Python 的简化版本,你们感受一下:
class TWAP:
def __init__(self, total_qty, total_seconds, interval=60):
self.total_qty = total_qty # 总数量
self.total_seconds = total_seconds # 总执行时间(秒)
self.interval = interval # 每次发单间隔(秒)
self.slices = total_seconds // interval
self.qty_per_slice = total_qty / self.slices
self.current_slice = 0
def next_order(self):
if self.current_slice >= self.slices:
return None
order = {
'time': self.current_slice * self.interval,
'qty': self.qty_per_slice
}
self.current_slice += 1
return order
# 使用示例
twap = TWAP(total_qty=1000000, total_seconds=600, interval=60)
for i in range(10):
print(twap.next_order())
你看,核心逻辑就这几行。但实际生产环境里,要考虑的东西多得多——比如滑点补偿、市场冲击、订单排队等等。我曾经在一个高频做市项目里,就因为 TWAP 的切片粒度太粗,被对手盘盯上了,连续吃了好几个亏。后来我把切片粒度从 60 秒改成了 5 秒,效果立竿见影。
二、VWAP:成交量加权平均价格
2.1 原理
VWAP 的全称是 Volume-Weighted Average Price。它比 TWAP 聪明一点——不是按时间均匀拆单,而是按历史成交量分布来拆。
你想想看,市场在开盘和收盘时成交量通常很大,中午休息时成交量很小。如果还用 TWAP 均匀发单,中午那会儿你发出去的订单可能半天成交不了,反而暴露了意图。
VWAP 的做法是:先统计过去 N 天的成交量分布,然后在成交量大的时段多发单,成交量小的时段少发单。这样你的订单更容易被市场消化。
每段下单量 = 总订单量 × (该时段历史成交量 / 总历史成交量)
2.2 实现
实现 VWAP 需要先准备历史成交量数据。我一般取过去 20 个交易日的分钟级成交量,做平均:
import numpy as np
class VWAP:
def __init__(self, total_qty, hist_volume_profile):
"""
hist_volume_profile: list of float, 每个时间段的平均成交量
"""
self.total_qty = total_qty
self.volume_profile = np.array(hist_volume_profile)
self.total_volume = self.volume_profile.sum()
self.weights = self.volume_profile / self.total_volume
self.current_slice = 0
def next_order(self):
if self.current_slice >= len(self.weights):
return None
qty = self.total_qty * self.weights[self.current_slice]
order = {
'time': self.current_slice, # 假设每段1分钟
'qty': round(qty, 0)
}
self.current_slice += 1
return order
# 假设一天有240个1分钟段,每个段的历史平均成交量
hist_vol = [1000 + np.random.randint(-200, 200) for _ in range(240)]
vwap = VWAP(total_qty=1000000, hist_volume_profile=hist_vol)
for i in range(10):
print(vwap.next_order())
这里有个坑,我必须提醒你:历史成交量分布 ≠ 今天的成交量分布。我记得有一次做 A 股 VWAP,那天突然出了个重大利好,开盘成交量暴增 5 倍,我的 VWAP 算法还按历史数据慢慢发单,结果收盘一看,执行价格比 VWAP 基准高了 0.3%。从那以后,我加了一个实时成交量修正模块——每 5 分钟重新计算一次剩余订单的分配权重。
三、POV:成交量百分比
3.1 原理
POV 的全称是 Percentage of Volume。它的思路和前两个完全不同——不关心时间,也不关心历史,只关心「我现在的订单量占市场成交量的比例」。
举个例子。你设定 POV = 10%,意思是:市场每成交 100 股,我就发 10 股。市场成交快,我就发得快;市场成交慢,我就发得慢。说白了,POV 是「跟屁虫」策略——市场怎么动,我就怎么动。
我个人特别喜欢 POV 的一点是:它天然具有自适应能力。市场活跃时你参与得多,市场冷清时你参与得少,不会像 TWAP 那样在流动性差的时候硬塞单子。
本次发单量 = 目标参与率 × 最近一段时间的市场成交量
3.2 实现
POV 的实现需要实时获取市场成交量数据。这里给一个简化版:
class POV:
def __init__(self, total_qty, participation_rate=0.1):
self.total_qty = total_qty # 总订单量
self.remaining_qty = total_qty
self.participation_rate = participation_rate # 目标参与率,如0.1表示10%
self.last_market_vol = 0
def update_market_volume(self, recent_vol):
"""更新最近一段时间的市场成交量"""
self.last_market_vol = recent_vol
def next_order(self):
if self.remaining_qty <= 0:
return None
# 按参与率计算本次发单量
order_qty = self.last_market_vol * self.participation_rate
# 不能超过剩余量
order_qty = min(order_qty, self.remaining_qty)
self.remaining_qty -= order_qty
return {
'qty': round(order_qty, 0),
'remaining': self.remaining_qty
}
# 模拟使用
pov = POV(total_qty=1000000, participation_rate=0.1)
market_volumes = [5000, 8000, 3000, 12000, 7000] # 模拟市场每分钟成交量
for vol in market_volumes:
pov.update_market_volume(vol)
order = pov.next_order()
print(f"市场成交{vol}股,我发{order['qty']}股,剩余{order['remaining']}股")
嗯,这里要注意:参与率不能设得太高。我曾经在一个小盘股上设了 15% 的 POV,结果市场一活跃,我的订单直接占了 30% 的成交量,瞬间把价格推高了 2%。后来我加了一个上限保护——单次发单量不超过总订单量的 5%。
四、三种算法对比
说了这么多,咱们来做个总结。我画了一张对比图,方便你理解:
最后,我再用表格给你对比一下三个算法的关键参数:
| 特性 | TWAP | VWAP | POV |
|---|---|---|---|
| 核心变量 | 时间 | 历史成交量 | 实时成交量 |
| 自适应能力 | 无 | 弱(依赖历史) | 强(实时跟随) |
| 市场冲击 | 中等 | 较低 | 低(但需控制参与率) |
| 实现难度 | 低 | 中 | 中高(需实时数据) |
| 典型参与率 | N/A | N/A | 5%-15% |
| 切片数量 | 10-50段 | 20-240段 | 动态(取决于市场) |
好了,这一章的内容就到这里。TWAP、VWAP、POV 这三个算法,说白了就是三种不同的「拆单哲学」。TWAP 相信时间,VWAP 相信历史,POV 相信市场。没有绝对的好坏,关键看你的交易场景和风险偏好。
下一章,咱们聊聊这些算法在实际生产环境中的「坑」——比如如何处理订单排队、如何应对市场冲击、如何做滑点补偿。这些都是我在项目里真金白银换来的教训,到时候一一分享给你们。
公众号:蓝海资料掘金营,微信deep3321