σμ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.6·~55分钟高级

真实的订单成交

路线进度模块 6/11 · 主题 6/6
6.6 真实的订单成交
—实验得分— / 100总 XP—连续天数—

成交并非必然

朴素的回测假设任意大小的订单都按数据里的价格成交。真实订单要面对三个问题。订单簿有多深:市价单先吃最优报价,再吃下一档,所以大单的平均价格更差。市场是否触及我的限价:限价单能拿到你要的价格或更好,但前提是交易走到了那里。以及我能吃多少:大单只能占某根K线成交量的一小部分,所以要分批成交。

def fill_market_order(book, quantity):
    remaining = quantity
    cost = 0.0
    filled = 0
    for price, size in book:                      # cheapest offer first
        if remaining <= 0:
            break
        take = min(size, remaining)
        cost += take * price
        filled += take
        remaining -= take
    if filled == 0:
        return 0.0, 0
    return cost / filled, filled

book = [(50.00, 300), (50.02, 500), (50.05, 1000)]
print(fill_market_order(book, 200))        # (50.0, 200)           inside the first level
print(fill_market_order(book, 600))        # (50.01, 600)          300 at 50.00 and 300 at 50.02
print(fill_market_order(book, 5000))       # (50.0333, 1800)      the whole book; 3,200 shares stay unfilled
# The average price and the slippage against the best offer grow with the size
def average_price(book, quantity):
    remaining, cost, filled = quantity, 0.0, 0
    for price, size in book:
        take = min(size, remaining)
        cost += take * price
        filled += take
        remaining -= take
    return cost / filled

book = [(50.00, 300), (50.02, 500), (50.05, 1000)]
for quantity in (100, 300, 800, 1800):
    avg = average_price(book, quantity)
    print(quantity, round(avg, 4), round((avg - 50.00) / 50.00 * 10000, 2), "bps above the best offer")

限价单与跳空

用K线数据(开盘、最低、最高)看不到K线内部的路径,只能看到它的极值。在 L 的买入限价单,如果这根K线的最低价触及 L 或更低就成交。成交价是 L,除非这根K线开盘就低于 L:那是市场跳空越过了你的限价,你按开盘价成交,价格更好。卖出限价单是镜像。仅仅被触及的限价单在现实中可能不成交,因为排在你前面的队伍先被满足,所以这是乐观的上界。

def limit_fill(side, limit, bar_open, bar_low, bar_high):
    if side == "buy" and bar_low <= limit:
        return min(bar_open, limit)             # a gap below the limit fills at the better open
    if side == "sell" and bar_high >= limit:
        return max(bar_open, limit)
    return None

print(limit_fill("buy", 49.95, 50.10, 49.90, 50.20))   # 49.95   traded down through the limit
print(limit_fill("buy", 49.95, 49.90, 49.80, 50.20))   # 49.9    opened below: filled at the open
print(limit_fill("buy", 49.95, 50.10, 50.00, 50.20))   # None    never reached
print(limit_fill("sell", 50.15, 50.00, 49.90, 50.20))  # 50.15

处理大订单

许多执行台使用参与率上限:比如不超过成交量的 10%。在成交 40,000 股的K线里,上限是 4,000 股(向下取整)。于是一笔 5,000 股的订单要两根K线,而不是一根。逐根处理是一个携带剩余数量的循环:每根K线成交 min(remaining, int(volume * participation)),订单完成后的K线成交为零。

def partial_fill(quantity, bar_volume, participation):
    return min(quantity, int(bar_volume * participation))

def fill_over_bars(quantity, volumes, participation):
    remaining = quantity
    fills = []
    for volume in volumes:
        filled = partial_fill(remaining, volume, participation)
        fills.append(filled)
        remaining -= filled
    return fills

fills = fill_over_bars(5000, [40000, 30000, 80000, 100000], 0.1)
print(fills, sum(fills))                      # [4000, 1000, 0, 0] 5000
成交模型决定你的诚实程度这里每一处简化都偏向同一个方向:假设限价单一触及就成交、假设你从不推动市场、假设成交量总是够。结果越依赖成交假设,就越应该用悲观规则来检验(价格穿过限价才成交、降低参与率上限),看优势是否还在。
  • 市价单逐档吃订单簿;平均价格按量加权,规模越大越差,订单簿薄时只能部分成交。
  • 限价单在K线触及限价时成交,若跳空越过则按开盘价成交;仅仅触及在现实中可能不成交。
  • 参与率上限把每根K线限制在 int(volume * participation);循环把剩余数量带过各根K线。

下一节:CAPM 与贝塔,度量收益中有多少来自市场。

实验:评分内容

  • 用 fill_market_order 逐档吃单25 分
  • 用 limit_fill 判断限价是否成交25 分
  • 用 partial_fill 遵守成交量上限25 分
  • 用 fill_over_bars 分时间推进订单25 分
资源管理器 · lab-92-1
▾ lab-92-1
…
bash · 浏览器内
learner@quantcraft:~/lab-92-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

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

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

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

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

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