σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academy学习路线
Ctrl K
AO
+1.2k 位学习者
登录加入 ↗
学习路线
AO
+1.2k 位学习者
登录
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python 学习路线›模块 11 · 生产系统与前沿模型›主题 11.4·~1小时专家

TWAP 执行

路线进度模块 11/11 · 主题 4/13
11.4 TWAP 执行
—实验得分— / 100总 XP—连续天数—

为什么不一次全发出去

用一张市价单买入 20 万股,你会一路吃穿订单簿:最初的股票按最优卖价成交,后面的以更差的价格成交,依此类推,所有看到你在买的人还会抬高报价。这种市场冲击是真实的成本,往往比佣金还大。执行算法把订单切成更小的子订单,随时间发出,让每一笔以更小的冲击被消化。最简单的是 TWAP(时间加权平均价格)算法:把数量分成相等的份,在起止时间之间均匀分布的各个时刻各发一份。

# Splitting 10 shares into 3 child orders: whole shares, earlier pieces take the extra
def twap_schedule(total, slices):
    size, extra = divmod(total, slices)
    return [size + (1 if index < extra else 0) for index in range(slices)]

print(twap_schedule(10, 3))                          # [4, 3, 3]
print(twap_schedule(200_000, 8))                     # [25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000]
print(sum(twap_schedule(10_001, 7)))                 # 10001   nothing is lost to rounding

计划:多少量、何时

TWAP 计划有两部分。数量把总数分成 n 份整数股,各份最多相差一股,且恰好加起来等于订单量:divmod(total, n) 给出基础数量以及有多少份多得一股。时间是在起止之间均匀分布的 n 个开始时刻:start + i * (end - start) / n。一个全天 20 万股、分 40 份的订单,就是每十分钟 5,000 股,由类似上一节的调度器发送。真实的实现会把数量和时间稍作随机化,因为完全规律的模式容易被别人发现并加以利用。

# When each slice starts: a trading day from 09:30 to 16:00 in 13 half-hour slices (seconds since midnight)
def slice_times(start, end, slices):
    step = (end - start) / slices
    return [start + index * step for index in range(slices)]

times = slice_times(9.5 * 3600, 16 * 3600, 13)
print(times[0], times[1] - times[0])                 # 34200.0 1800.0   09:30 and a slice every 30 minutes
print(times[-1] / 3600)                              # 15.5             the last slice starts at 15:30, the end (16:00) is not a start time

衡量:平均成交价与滑点

各份成交之后,平均成交价是按数量加权的成交价平均值 sum(price * quantity) / sum(quantity):大笔成交比小笔更重要,所以价格的简单平均是错的。要评判结果,把它与到达价格比较,也就是做出决定那一刻的市场价格。差额就是滑点,以基点表示(1 bp 等于 0.01%):买入时为 (平均价 - 到达价) / 到达价 * 10,000,卖出时符号相反,因此正数永远代表成本。这就是执行落差(implementation shortfall)的核心,也是给一次执行打分的标准方法。

# Average fill price (quantity-weighted) and slippage against the arrival price, in basis points
def average_fill_price(fills):
    return sum(price * quantity for price, quantity in fills) / sum(quantity for _, quantity in fills)

def slippage_bps(avg_price, arrival_price, side):
    sign = 1 if side == "buy" else -1
    return sign * (avg_price - arrival_price) / arrival_price * 10000

fills = [(100.0, 400), (100.2, 300), (100.4, 300)]
average = average_fill_price(fills)
print(round(average, 2), sum(price for price, _ in fills) / 3)      # 100.18 100.2   the plain mean of the prices is not the answer
print(round(slippage_bps(average, 100.0, "buy"), 2))                # 18.0   a buy that averaged 100.18 against an arrival of 100.00 cost 18 bp
print(round(slippage_bps(average, 100.0, "sell"), 2))               # -18.0  the same prices would have been a gain for a seller
# A toy run: the price drifts up while we buy 10 slices; the slippage is what the drift cost us
prices = [100.00, 100.03, 100.05, 100.04, 100.08, 100.10, 100.09, 100.12, 100.15, 100.14]     # price at each slice time
pieces = [100] * 10                                                                         # TWAP: equal child orders

arrival = prices[0]
fills = list(zip(prices, pieces))
average = sum(p * q for p, q in fills) / sum(pieces)
print(round(average, 4))                             # 100.08
print(round((average - arrival) / arrival * 10000, 1))      # 8.0   bp of slippage for buying into a rising market
可预测就可被利用TWAP 无视市场:在安静的时刻和繁忙的时刻交易相同的数量,它规律的足迹可能被识别并被抢先交易。当你对成交量没有看法、又想要简单时,它是个合理的基准。改进是下一节的内容:跟随一天的成交量曲线(VWAP)、给你在每个区间成交量中所占的份额设上限、让数量和时间随机化,以及在价格离你而去时停下或放慢。在信任一个算法之前,务必在相同的日子里把它的滑点与一个简单基准做比较。
  • 大订单会推动价格;执行算法把更小的子订单随时间发出。TWAP 在均匀分布的时刻发出相等的份额。
  • 数量:用 divmod(total, n),多出的股数放在前面的份里;时间:start + i * (end - start) / n。
  • 用按数量加权的平均成交价相对到达价格的基点数来给执行打分;正数表示成本。

下一节:VWAP 执行,跟随一天的成交量而不是时钟。

实验:评分内容

  • 用 twap_schedule 确定每一份的数量25 分
  • 用 slice_times 在时间上安排各份25 分
  • 用 average_fill_price 平均你的成交25 分
  • 用 slippage_bps 衡量成本25 分
资源管理器 · lab-133-1
▾ lab-133-1
…
bash · 浏览器内
learner@quantcraft:~/lab-133-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

lab-133-1 中的所有内容将恢复为初始文件,终端也会被清空。你当前的解答将丢失,且无法撤销。

将被重置
  • main.py → 初始代码
  • 终端历史与测试输出
  • 未提交的更改
将被保留
  • 实验最高得分
  • 你的 XP 与连续天数
  • 讲解进度与笔记

登录后即可运行你的代码。

所有讲解均可免费阅读。创建账户或登录后,即可运行代码、运行测试并提交此实验。

  • 浏览器内 IDE 与 bash 终端,无需安装
  • 即时评分、XP 以及你在学习路线中的位置
创建账户登录
或
使用 GitHub 继续