σμ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 AcademyRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Rutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Ruta de Python›Módulo 6 · Backtesting y costes de trading›Tema 6.5·~45 minAvanzado

Deslizamiento (slippage)

Posición en la rutaMódulo 6 de 11 · Tema 5 de 6
6.5 Deslizamiento (slippage)
—Puntuación del lab— / 100XP total—Racha—

El precio que no obtienes

Un backtest opera al precio de sus datos; una orden real se ejecuta en otro. El deslizamiento (slippage) es esa diferencia: compras algo más alto que el precio visto y vendes algo más bajo. Tiene dos fuentes. El diferencial: una orden de mercado compra al ask y vende al bid, así que pagas la mitad del diferencial en cada sentido respecto al precio medio. Y el impacto de mercado: una orden grande consume las mejores cotizaciones y empuja el precio en su contra.

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

Medirlo frente a una referencia

Para medir el deslizamiento hace falta una referencia: el precio de llegada, el precio medio cuando se decidió la orden. El déficit lleva signo para que positivo signifique siempre coste: en una compra, fill - arrival; en una venta, arrival - fill, en puntos básicos del precio de llegada. La misma ejecución mala para un comprador es buena para un vendedor, por eso el signo depende del lado. Las órdenes grandes se ejecutan por partes, así que la cifra a informar es el precio medio de ejecución ponderado por volumen.

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

El impacto crece con el tamaño

El diferencial es un cargo fijo; el impacto depende de cuánto operas. Una regla práctica bien contrastada es la ley de la raíz cuadrada: el impacto esperado como fracción del precio es aproximadamente k * volatility * sqrt(quantity / ADV), donde ADV es el volumen diario medio y k una constante cercana a 1. Cuadruplicar la orden solo duplica el impacto, así que dividir una orden grande en el tiempo ayuda, pero menos de lo que se espera.

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
Añade deslizamiento a cada backtestIncluso un modelo tosco es mejor que ninguno: cobra la mitad del diferencial cotizado en cada operación y un término de impacto que crece con el tamaño de la orden, y mira si la estrategia sobrevive. Si solo funciona con deslizamiento cero, no funciona. La comprobación honesta es comparar el deslizamiento supuesto con las ejecuciones reales, y reducir la hipótesis solo cuando los datos lo permitan.
  • Una compra se ejecuta por encima del precio medio y una venta por debajo: mid * (1 +/- bps / 10000); el diferencial solo cuesta la mitad de su ancho en cada sentido.
  • Mida frente al precio de llegada, con signo para que positivo signifique coste; las ejecuciones parciales se combinan con un promedio ponderado por volumen.
  • El impacto sigue la ley de la raíz cuadrada k * volatility * sqrt(quantity / ADV): cuatro veces el tamaño, el doble de impacto.

A continuación: ejecuciones de órdenes realistas, simular cómo se ejecutan realmente las órdenes contra un libro y barras de datos.

Lab: qué se califica

  • Poner precio a la ejecución con fill_price25 pts
  • Medir el déficit con slippage_bps25 pts
  • Combinar ejecuciones parciales con average_fill25 pts
  • Pronosticar el impacto con sqrt_impact_bps25 pts
Explorador · lab-91-1
▾ lab-91-1
…
bash · en el navegador
learner@quantcraft:~/lab-91-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-91-1 vuelve a los archivos iniciales y se limpia el terminal. Tu solución actual se perderá: no se puede deshacer.

Se restablecerá
  • main.py → código inicial
  • Historial del terminal y salida de pruebas
  • Cambios sin enviar
Se conservará
  • Mejor puntuación del lab
  • Tu XP y tu racha
  • Progreso de la lección y notas

Inicia sesión para ejecutar tu código.

Todas las clases se pueden leer gratis. Crea una cuenta o inicia sesión para ejecutar código, lanzar las pruebas y enviar este lab.

  • IDE en el navegador con terminal bash — sin instalar nada
  • Corrección instantánea, XP y tu lugar en la ruta
Crear una cuentaIniciar sesión
O
Continuar con GitHub