σμ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万株を1回の成行注文で買うと、板を食い進むことになります。最初の株は最良のアスクで約定し、次はより悪い価格で、と続き、あなたが買っていると見た参加者は価格を引き上げます。このマーケットインパクトは実際のコストで、手数料より大きいことも珍しくありません。執行アルゴリズムは注文を、時間をかけて送る小さな子注文に分割し、それぞれがより小さなインパクトで吸収されるようにします。最も単純なのが TWAP(時間加重平均価格)アルゴリズムで、数量を等しい部分に分け、開始から終了までの等間隔の時刻ごとに1つずつ送ります。

# 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 のスケジュールは2つの部分からなります。数量は、合計を、差が高々1株の整数株の n 個に分け、合計がちょうど注文数量になるようにします。divmod(total, n) が基本のサイズと、1株多く受け取る個数を与えます。時刻は、開始と終了の間に等間隔に並ぶ n 個の開始時刻 start + i * (end - start) / n です。1日がかりで20万株を40スライスにすると、10分ごとに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) です。大きな約定は小さな約定より重いので、価格の単純平均は誤りです。結果を評価するには、意思決定の時点の市場価格である到着価格と比べます。その差がスリッページで、ベーシスポイント(1bp は 0.01%)で表します。買いなら (平均 - 到着) / 到着 * 10,000、売りは符号が逆なので、正の値は常にコストです。これがインプリメンテーション・ショートフォール、つまり執行を採点する標準的な方法の核心です。

# 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 は市場を無視します。静かな時間帯でも活発な時間帯でも同じ量を取引するため、その規則的な足跡は見破られ、先回りされる可能性があります。出来高について見解がなく、単純さを求めるときの妥当な基準です。改善は次のトピックで扱います。1日の出来高カーブに従う(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で続ける