σμ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 学习路线›模块 6 · 回测与交易成本›主题 6.5·~45分钟高级

滑点

路线进度模块 6/11 · 主题 5/6
6.5 滑点
—实验得分— / 100总 XP—连续天数—

你拿不到的价格

回测按数据里的价格成交,而真实订单在别处成交。滑点就是这个差别:你买得比所见价格略高,卖得略低。它有两个来源。一是价差:市价单按卖一价买、按买一价卖,所以相对中间价,每个方向要付一半的价差。二是市场冲击:大单会吃掉最好的报价,并把价格推向对自己不利的方向。

def fill_price(mid, side, slippage_bps):
    if side == "buy":
        return mid * (1 + slippage_bps / 10000)
    if side == "sell":
        return mid * (1 - slippage_bps / 10000)
    raise ValueError("side must be 'buy' or 'sell'")

print(round(fill_price(50.0, "buy", 4), 4))        # 50.02   pays more
print(round(fill_price(50.0, "sell", 4), 4))       # 49.98   receives less
# Crossing the spread: a market buy pays the ask, a market sell receives the bid
bid, ask = 49.98, 50.02
mid = (bid + ask) / 2
half_spread_bps = (ask - bid) / 2 / mid * 10000
print(mid, round(half_spread_bps, 2))                     # 50.0 4.0   each way, relative to the mid price
print(round((ask - mid) / mid * 10000, 2), round((mid - bid) / mid * 10000, 2))   # 4.0 4.0

对照基准来度量

要度量滑点,需要一个参照:到达价,即下单决定时的中间价。差额带符号,使正数总是表示你吃了亏:买入为 fill - arrival,卖出为 arrival - fill,以到达价的基点表示。对买方不利的同一笔成交对卖方有利,所以符号取决于方向。大单会分批成交,因此应报告按成交量加权的平均成交价。

import numpy as np

def slippage_bps(fill, arrival, side):
    if side == "buy":
        return (fill - arrival) / arrival * 10000
    if side == "sell":
        return (arrival - fill) / arrival * 10000
    raise ValueError("side must be 'buy' or 'sell'")

def average_fill(prices, quantities):
    prices, quantities = np.asarray(prices, dtype=float), np.asarray(quantities, dtype=float)
    return float(np.sum(prices * quantities) / np.sum(quantities))

# Buying 600 shares in three pieces; the price at arrival was 50.00
avg = average_fill([50.0, 50.1, 50.3], [100, 200, 300])
print(round(avg, 4))                                      # 50.1833
print(round(slippage_bps(avg, 50.0, "buy"), 2))           # 36.67 bps: it cost us
print(round(slippage_bps(avg, 50.0, "sell"), 2))          # -36.67: the same fills would have helped a seller

冲击随规模增长

价差是固定费用,而冲击取决于你交易多少。一条经过充分检验的经验法则是平方根定律:预期冲击占价格的比例约为 k * volatility * sqrt(quantity / ADV),其中 ADV 是日均成交量,k 是接近 1 的常数。订单放大到 4 倍,冲击只增加到 2 倍,所以把大单拆开分时间执行有帮助,但帮助没有想象的那么大。

import math

def sqrt_impact_bps(volatility, quantity, adv, k=1.0):
    return k * volatility * math.sqrt(quantity / adv) * 10000

# A stock with 2% daily volatility and 1,000,000 shares of average daily volume
for shares in (10_000, 50_000, 200_000, 800_000):
    print(shares, round(sqrt_impact_bps(0.02, shares, 1_000_000), 1), "bps")
# 10,000: 20.0, 50,000: 44.7, 200,000: 89.4, 800,000: 178.9
每个回测都要加上滑点哪怕是粗糙的模型也比没有强:每笔交易收取报价价差的一半,再加一个随订单规模增长的冲击项,然后看策略能否存活。如果只有在零滑点时才有效,那它就是无效的。诚实的做法是把假设的滑点与实盘成交对比,只有数据允许时才下调假设。
  • 买入成交在中间价之上,卖出在其下:mid * (1 +/- bps / 10000);仅价差每个方向就要花其宽度的一半。
  • 对照到达价度量,符号使正数表示成本;部分成交用成交量加权平均合并。
  • 冲击遵循平方根定律 k * volatility * sqrt(quantity / ADV):规模 4 倍,冲击 2 倍。

下一节:逼真的订单成交,模拟订单如何真正对照订单簿和K线数据成交。

实验:评分内容

  • 用 fill_price 计算成交价25 分
  • 用 slippage_bps 衡量差额25 分
  • 用 average_fill 合并部分成交25 分
  • 用 sqrt_impact_bps 预测冲击25 分
资源管理器 · lab-91-1
▾ lab-91-1
…
bash · 浏览器内
learner@quantcraft:~/lab-91-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

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

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

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

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

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