σμ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 AcademyParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Parcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Python›Module 6 · Backtesting et coûts de trading›Sujet 6.5·~45 minAvancé

Le glissement (slippage)

Position dans le parcoursModule 6 sur 11 · Sujet 5 sur 6
6.5 Le glissement (slippage)
—Score du lab— / 100XP total—Série—

Le prix que vous n'obtenez pas

Un backtest négocie au prix de ses données ; un ordre réel est exécuté ailleurs. Le glissement (slippage) est cette différence : on achète un peu plus haut que le prix vu, et on vend un peu plus bas. Il a deux sources. L'écart : un ordre au marché achète à l'ask et vend au bid, donc on paie la moitié de l'écart de chaque côté par rapport au prix milieu. Et l'impact de marché : un gros ordre épuise les meilleures cotations et pousse le prix contre lui.

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

La mesurer par rapport à une référence

Pour mesurer le glissement, il faut une référence : le prix d'arrivée, le prix milieu quand l'ordre a été décidé. Le manque à gagner est signé de sorte que positif signifie toujours un coût : pour un achat, fill - arrival ; pour une vente, arrival - fill, en points de base du prix d'arrivée. La même exécution mauvaise pour un acheteur est bonne pour un vendeur, d'où la dépendance du signe au sens. Les gros ordres sont exécutés par morceaux, donc le chiffre à rapporter est le prix moyen d'exécution pondéré par les volumes.

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

L'impact croît avec la taille

L'écart est un coût fixe ; l'impact dépend de ce que l'on négocie. Une règle empirique bien testée est la loi de la racine carrée : l'impact attendu en fraction du prix vaut environ k * volatility * sqrt(quantity / ADV), où ADV est le volume quotidien moyen et k une constante proche de 1. Quadrupler l'ordre ne fait que doubler l'impact, donc fractionner un gros ordre dans le temps aide, mais moins qu'on ne l'espère.

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
Ajoutez le glissement à chaque backtestMême un modèle grossier vaut mieux que rien : facturez la moitié de l'écart coté sur chaque transaction et un terme d'impact croissant avec la taille de l'ordre, puis voyez si la stratégie survit. Si elle ne marche qu'avec un glissement nul, elle ne marche pas. La vérification honnête consiste à comparer le glissement supposé avec les exécutions réelles, et à ne baisser l'hypothèse que si les données le permettent.
  • Un achat est exécuté au-dessus du prix milieu et une vente en dessous : mid * (1 +/- bps / 10000) ; l'écart seul coûte la moitié de sa largeur dans chaque sens.
  • Mesurez par rapport au prix d'arrivée, signé pour que positif signifie coût ; les exécutions partielles se combinent par une moyenne pondérée par les volumes.
  • L'impact suit la loi de la racine carrée k * volatility * sqrt(quantity / ADV) : quatre fois la taille, deux fois l'impact.

À suivre : des exécutions d'ordres réalistes, simuler comment les ordres sont réellement exécutés contre un carnet et des barres.

Lab : ce qui est noté

  • Chiffrer l'exécution avec fill_price25 pts
  • Mesurer le manque à gagner avec slippage_bps25 pts
  • Fusionner les exécutions partielles avec average_fill25 pts
  • Prévoir l'impact avec sqrt_impact_bps25 pts
Explorateur · lab-91-1
▾ lab-91-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-91-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-91-1 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes

Connectez-vous pour exécuter votre code.

Tous les cours sont gratuits à lire. Créez un compte ou connectez-vous pour exécuter du code, lancer les tests et soumettre ce lab.

  • IDE dans le navigateur avec terminal bash — rien à installer
  • Correction instantanée, XP et votre place dans le parcours
Créer un compteSe connecter
OU
Continuer avec GitHub