← 返回模块
lesson-4-5-3-2-schedule-based-algos-vwap-twap-povbeta 可读 · 未来付费校验通过内容版本 2026-05-28

时间表型算法:VWAP、TWAP 与 POV

4.5.3 · 执行算法 · 量化全流程

一家上海私募的交易员盯着一张母单:沪深300 ETF(510300.SH)800,000 股买入,VWAP 基准,窗口设到全天。基金经理只关心基点数字,对收盘集合竞价并不挑剔。交易员调出 20 日滚动中位的盘中成交量曲线,看到 A 股典型的「双峰带午休」形状——上午 9:30 开盘一峰,11:30 前再起一峰,13:00 复盘后又有一峰,14:55 收盘集合竞价之前最后一拉——然后开始切片。本课要搭的,就是驱动这些切片时点与切片大小的算法本身:从定义到可工作伪代码,覆盖三个时间表家族——VWAP、TWAP、POV。

学完本课你会拥有:每个算法的母单到子单切片器、关于「何时 TWAP 战胜 VWAP」的量化答案、POV 残量风险的显式规则、三种失败模式的信号。本课不挑选时间表 vs. IS——那是第 4 课;也不估市场冲击系数——那是模块 4.5.2。

VWAP(成交量加权平均价):用成交量曲线搭时间表

VWAP 按区间 期望 成交量比例切片。令 U(t)U(t) 为区间 tt 占当日总成交量的期望比例(tU(t)=1\sum_t U(t) = 1)。则区间 tt 的子单大小为

qt=QU(t).q_t = Q \cdot U(t).

基准——区间内公开盘面的成交量加权平均价(VWAP)——为

VWAPinterval=tvtPˉttvt,\text{VWAP}_{\text{interval}} = \frac{\sum_t v_t \bar P_t}{\sum_t v_t},

其中 vtv_t 是该区间实际公开成交量,Pˉt\bar P_t 是该区间实际公开均价。如果你的时间表匹配 U(t)U(t),且子单成交跟得上 Pˉt\bar P_t(除去价差),算法的平均成交价按构造等于 VWAP 基准,滑点只来自半买卖价差 12s\tfrac{1}{2}s 与你自身订单流(order flow)带来的市场冲击 h(qt)h(q_t)。​​偏离 U(t)U(t) 会产生 VWAP 滑点期望为零、但方差被放大​​——这正是到达价基准会暴露的时间风险。

估计 U(t)U(t)——5 分钟桶配方(executable Python snippet)

市场冲击(market impact)与订单流(order flow)分析共享同一分桶口径,下面的可工作伪代码也用于这些模块。

A 股的工业做法用 9:30–11:30、13:00–15:00 连续竞价时段的 20 日滚动中位、5 分钟桶。注意 11:30–13:00 的 ​午休​​——A 股的 U(t)U(t)带间断 的两段,不是单段 U 形;这是把美股 VWAP 算法直接搬到 A 股最常见的错误。510300.SH 在 5 分钟桶下覆盖 48 桶(上午 24 桶 + 下午 24 桶)加上收盘集合竞价。伪代码:

import numpy as np

# trades: DataFrame,列 [date, bucket_idx, shares]
# bucket_idx: 0..23 上午, 24..47 下午, 48 收盘集合竞价
daily_total = trades.groupby('date')['shares'].sum()
per_bucket = trades.groupby(['date', 'bucket_idx'])['shares'].sum() \
                   .div(daily_total, level='date')
U = per_bucket.unstack('bucket_idx').rolling(20).median().iloc[-1]
U = U / U.sum()  # 归一化使权重和为 1

对 510300.SH,这通常重现「双峰 + 午休 + 收盘小拉」的形状:9:30 桶约占 3.5%,11:25 桶约占 2.5%,13:00 桶约占 2.0%,14:55 收盘集合竞价桶约占 1.5%–2.5%。一个忽略午休的曲线会把 13:00 复盘后的「热身」成交分散到一个不存在的连续区间里,造成系统性误调度。

TWAP(时间加权平均价):什么时候均匀打败曲线

TWAP 取 U(t)=1/NU(t) = 1/NNN 个等长区间——一条平直的时间表。子单大小是

qt=Q/N.q_t = Q / N.

TWAP 看上去比 VWAP 朴素,但它在三种场合占优:

  1. ​平直成交量曲线的合约。​ 单段连续竞价合约——CFFEX 指数期货(IF、IC、IH)——的 U(t)U(t) 几乎平直。从平直过程里估出一条弯曲的 U(t)U(t) 只是把样本噪声注入时间表;TWAP 干脆不估。
  2. ​非平稳成交量曲线。​ 当当日实现的成交量分布漂移(财报季单只股票、央行降息日 510300.SH),20 日中位的 U(t)U(t) 相对当日真曲线是有偏的。TWAP 的「平直」假设错得比 VWAP 的「陈旧曲线」少。
  3. ​无集合竞价的单段合约。​ 没有开盘 / 收盘集合竞价,VWAP 的集合竞价偏差问题就不存在;跟曲线的边际价值也降下来。

一个有用的诊断指标:如果 20 日滚动 MAD(中位绝对偏差)除以桶中位超过 0.4,曲线已不稳定到可下注的程度——这只名字 / 这周切换 TWAP。

POV:参与,而不是调度

POV 不承诺轨迹。它承诺一个 参与率 ρ\rho——比如 5%——并让实际公开成交量驱动子单大小。区间 tt 实际公开成交量为 vtv_t 时,子单为

qt=min(ρvt, Qremaining).q_t = \min\big(\rho \cdot v_t,\ Q_{\text{remaining}}\big).

POV 的强项是自适应:流动性强时多打,流动性枯时少打。它的弱点是 残量风险。若当日实际成交量低于预期,tρvt<Q\sum_t \rho v_t < Q,算法会在日终留下未完成残量。基金经理这时面临选择——把残量推进收盘集合竞价(510300.SH 上代价低,单只小盘 A 股上代价高),或者隔夜留仓承担跳空风险,外加 T+1 结算的二级约束(见术语表 t-plus-1-settlement,A 股 T 日买入 T+1 才能卖出)。盘前分析通常在 Q/(ρADV)>0.5Q / (\rho \cdot \text{ADV}) > 0.5 时预警——5% POV 想清掉一笔母单,至少需要两个正常交易日的流动性。

A 股私募桌的参与率上限通常压在 5%–10% ADV 以下,避开涨跌停(limit-up / limit-down,见术语表)触发——这是 A 股市场结构对 POV 配置的硬约束,与美股 10% 的经验上限不同。

子单切片器:可工作伪代码

生产环境的切片器不止 q_t = Q * U(t)。它加入 ​抖动(jitter)​​——每个桶内随机化下单时点——以打破对周期性 VWAP 印迹的预测。完整骨架:

import numpy as np

def schedule_vwap(Q, U, time_grid, jitter_sec=15, seed=None):
    """返回 [(send_time, child_qty)] 形式的 VWAP 时间表."""
    rng = np.random.default_rng(seed)
    schedule = []
    cum = 0
    for t, frac in zip(time_grid, U):
        target_cum = Q * (cum + frac)
        child_qty = int(round(target_cum - cum * Q))
        cum += frac
        offset = rng.uniform(-jitter_sec, jitter_sec)
        schedule.append((t + offset, child_qty))
    # 取整修正:剩余股数分配到最大桶
    total = sum(qty for _, qty in schedule)
    if total != Q:
        diff = Q - total
        i_max = int(np.argmax([qty for _, qty in schedule]))
        send_time, qty = schedule[i_max]
        schedule[i_max] = (send_time, qty + diff)
    return schedule

TWAP 是同一函数取 U(t)=1/NU(t) = 1/N。POV 把静态 U 换成回调,每个区间读取实际 vtv_t 后重新计算 qt=ρvtq_t = \rho v_t,并受剩余母单约束。

这条把三个失败模式都连起来的核心公式:

Formula Explorer

q_t = Q * U_t

三种经典失败模式

每个时间表算法都有一种正典的「亏钱方式」,且每一种都映射到一个可测量的盘后 TCA 信号。

  1. ​VWAP 在开盘附近被反向利用。​ 反向流动性算法识别出 VWAP 节奏,把买侧子单挤到 9:30 开盘集合竞价的印迹里——此处明面流动性最薄。VWAP 基准本身被推得不利于你(开盘价差远宽于 9:35 之后的稳态价差)。TCA 信号:第一桶滑点持续为负,即使全窗口滑点接近零。

  2. ​TWAP 错过成交量集群。​ 央行降息公告在 14:30 引爆 5 倍成交量;TWAP 的平直时间表继续发 Q/NQ/N,错过明面流动性。滑点表现为错过桶的价差走宽以及相对新 VWAP 的机会成本。TCA 信号:事件日实际 VWAP 滑点显著上行,即使子单大小均匀。

  3. ​POV 追着自己的脚印跑。​ 母单相对 ADV 大时,算法自身印迹支配了区间成交量度量。POV 看到高 vtv_t,发送 ρvt\rho v_t 的子单;该子单印迹又抬高下一区间 vtv_t;反馈环让实际参与率超出名义 ρ\rho。TCA 信号:实际参与率超出配置 ρ\rho 一个可测量边距(Q/ADV>0.05Q/\text{ADV} > 0.05 时常见 1.2 倍–1.5 倍)。

A 股的额外注脚:印花税(stamp-duty)只压卖侧,是固定楔形成本,会出现在 TCA 中但不进入时间表调度。第 4 课会展开。

区域锚定算例(region-anchored example):510300.SH,800k VWAP 买入

母单 800,000 股全天 VWAP。U(t)U(t) 在 9:30 桶 3.5%、14:55 收盘集合竞价桶 2.0%。切片:q0=28,000q_0 = 28{,}000q48=16,000q_{48} = 16{,}000Pˉ¥3.852\bar P \approx ¥3.852、到达中价 ¥3.850,全笔交易成本约 4–6 bp。

模块 4.5.1 负责撮合规则把时间表映射为 Pˉ\bar P;模块 4.5.2 负责 h(qt)h(q_t) 的线性冲击模型。

练习

Exercise

你拿到沪深300 ETF(510300.SH)300,000 股买单,VWAP 基准,窗口 10:00–11:00。该窗口 20 日滚动中位 U(t)U(t) 大致平直,每 5 分钟桶约 1%。10:30–10:35 桶实际公开成交量飙到中位的 4 倍(一份提前流出的研报)。写下:(a) VWAP 时间表在该桶的 qtq_t,(b) TWAP 时间表在该桶的 qtq_t,(c) 哪个时间表产出更低的 VWAP 基准滑点、为什么,(d) 5% POV 会不会比两者更好。

提示
VWAP 用的是飙升 之前 估出的 U(t)U(t),无论 10:30 发生什么,qt=Q0.01q_t = Q \cdot 0.01 不变。TWAP 同样事前承诺一条平直曲线。
提示
POV 对实际 vtv_t 反应。在 4 倍中位时,5% POV 会在 10:30–10:35 发出 4 倍基线子单。把它与 VWAP/TWAP 的静态承诺对比。
提示
VWAP 基准滑点奖励「跟着印迹走」。基准本身被成交量集群推高。哪个时间表最贴近这个变动中的基准?

与第 3 课的衔接

至此你能端到端搭一个时间表型算法:估 U(t)U(t)、切片、加抖动、识别失败模式。下一课从时间表迈向最优化。我们把静态 U(t)U(t) 换成 Almgren–Chriss 最优轨迹——那个 在到达中价基准下最小化预期成本加上风险厌恶加权方差的时间表——并把单一场所执行换成跨 SSE、SZSE 主板与私募内部撮合网络的智能订单路由。交易成本会开始呈现时间表算法做不出来的形状。把本课的切片器骨架带进第 3 课;下一课要做的,是把 U 参数替换成 Almgren–Chriss 的输出。