σμ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—連続日数—

手に入らない価格

バックテストはデータ上の価格で売買しますが、実際の注文は別の価格で約定します。スリッページはその差で、見た価格より少し高く買い、少し安く売ることになります。原因は2つあります。スプレッド:成行注文は売り気配で買い、買い気配で売るので、仲値に対して片道スプレッドの半分を払います。そしてマーケット・インパクト:大きな注文は良い気配を食い尽くし、自分に不利な方向へ価格を押します。

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倍

次は現実的な注文の約定です。板情報やバーのデータに対して注文が実際にどう約定するかをシミュレートします。

ラボ:採点される内容

  • 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で続ける