Lección 4.5.3 · 70 min
Paper trading, shadow trading y seguimiento del slippage
El paper trading promete lo que el backtest promete: ejecuciones al precio medio, sin horquilla y casi sin latencia. Esta lección compara ejecución a ejecución el backtest, el paper y la cuenta real de un mismo sistema, separa el retraso del deslizamiento, recupera los parámetros de deslizamiento plantados por tipo de orden y momento, y monta el seguimiento que habría avisado en la primera semana.
- Módulo
- 4.5 · Sistema de trading en vivo
- Duración
- 70 min
- Requisitos
- 4.5.1 · Arquitectura de un sistema en vivo: de los datos a la reconciliación, 4.5.2 · Gestión de órdenes: estados, fills parciales, cancelaciones y reconexiones, 4.3.1 · Implementation shortfall, VWAP, TWAP y Almgren-Chriss, 2.4.7 · Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos
Lecciones del nivel 4
4.1 · Teoría y optimización de carteras
- 4.1.1 Markowitz: frontera eficiente y restricciones reales
- 4.1.2 Estimación de covarianzas: shrinkage, modelos factoriales y denoising
- 4.1.3 Black-Litterman: del equilibrio del mercado a las visiones
- 4.1.4 Risk parity, mínima varianza, HRP y HERC
- 4.1.5 Optimización con CVaR, drawdown, rotación y costes
- 4.1.6 Modelos factoriales: CAPM, Fama-French, PCA y atribución
- 4.1.7 Rebalanceo y carteras de estrategias
- 4.1.8 Herramientas de carteras: Riskfolio-Lib, PyPortfolioOpt, skfolio y bt
4.2 · Gestión del riesgo y tamaño de posición
- 4.2.1 Riesgo por operación, stops y riesgo de ruina
- 4.2.2 Criterio de Kelly y volatility targeting: cuánto arriesgar
- 4.2.3 VaR, Expected Shortfall y backtesting del VaR
- 4.2.4 Stress testing y riesgos de correlación, liquidez, gap y contraparte
- 4.2.5 Control del drawdown y kill switch
- 4.2.6 Riesgo de derivados: griegas de cartera, margen y liquidaciones
4.3 · Microestructura y ejecución
4.4 · Brokers y APIs
- 4.4.1 Elegir bróker desde España o la UE: regulación, activos, costes y API
- 4.4.2 Interactive Brokers: TWS e IB Gateway, ib_async, contratos, datos y órdenes
- 4.4.3 Alpaca, Tradier, MetaTrader 5 con Python y OANDA v20
- 4.4.4 APIs de exchanges cripto: ccxt, Binance, Bybit, Kraken y Coinbase
- 4.4.5 Credenciales seguras: variables de entorno, gestores de secretos y permisos mínimos
4.5 · Sistema de trading en vivo
- 4.5.1 Arquitectura de un sistema en vivo: de los datos a la reconciliación
- 4.5.2 Gestión de órdenes: estados, fills parciales, cancelaciones y reconexiones
- 4.5.3 Paper trading, shadow trading y seguimiento del slippage
- 4.5.4 Programación de tareas y alertas: cron, APScheduler, Prefect, Telegram, Slack y email
- 4.5.5 Tolerancia a fallos: idempotencia, estado persistente y reconciliación al arrancar
- 4.5.6 NautilusTrader o LEAN en real: del backtest a producción con el mismo código
Tres precios para la misma orden
El checklist de la lección 2.5.8 terminaba en el paper trading: la estrategia corre en tiempo real con órdenes simuladas durante un periodo fijado de antemano, para comprobar que lo que supone el backtest ocurre. Esta lección lo hace con números. El sistema de los datos del nivel (lecciones 4.5.1 y 4.5.2) operó en una cuenta paper del 2 de septiembre al 10 de octubre de 2025 y en una cuenta real del 13 de octubre al 31 de diciembre, con las mismas reglas y 1.000.000 USD en cada una. Para cada señal hay tres precios:
- Backtest (
vivo4_backtest_fills.csv): lo que supone el backtest. El medio a la hora de la señal (15 minutos antes del cierre) para las órdenes a mercado y límite, el cierre oficial para las de cierre y la apertura de la sesión siguiente para las de los lunes que se ejecutan al abrir. Sin costes. - Medio al enviar (
precio_medio_envioenvivo4_ordenes.csv): el precio medio del mercado cuando el OMS envía la orden. Es la referencia del deslizamiento. - Ejecución: el precio de cada ejecución que devolvió el broker.
Fuente Mermaid
flowchart TB B[Precio backtest] --> R[Retraso] R --> M[Medio al enviar] M --> D[Deslizamiento] D --> E[Ejecución]
La diferencia entre backtest y ejecución se parte en dos. El retraso es lo que se mueve el mercado entre la hora que supone el backtest y la del envío: si la señal no tiene información de corto plazo, su media es cero, pero su ruido es grande. El deslizamiento es lo que cuesta ejecutar frente al medio en el momento de enviar: media horquilla, impacto, latencia. Es lo que un modelo de costes (lecciones 2.4.7 y 4.3.1) tiene que estimar, y lo que el paper no tiene.
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
ordn = pd.read_csv("vivo4_ordenes.csv",
parse_dates=["ts_envio", "fecha"])
ev = pd.read_csv("vivo4_eventos_broker.csv")
bt = pd.read_csv("vivo4_backtest_fills.csv")
ej = (ev[ev["evento"].isin(["parcial", "ejecutada"])]
.drop_duplicates("id_ejecucion")) # lección 4.5.2
x = (ej[["id_orden_cliente", "id_ejecucion", "cantidad_ultima",
"precio_ultimo", "comision", "ts_broker"]]
.merge(ordn, on="id_orden_cliente")
.merge(bt[["id_senal", "precio_backtest", "supuesto"]],
on="id_senal"))
x["s"] = np.where(x["lado"] == "compra", 1, -1)
x["efectivo"] = x["cantidad_ultima"] * x["precio_ultimo"]
pb = lambda a, b: x["s"] * (a / b - 1) * 1e4 # + = peor
x["total"] = pb(x["precio_ultimo"], x["precio_backtest"])
x["retraso"] = pb(x["precio_medio_envio"], x["precio_backtest"])
x["desliz"] = pb(x["precio_ultimo"], x["precio_medio_envio"])
print(len(x), "ejecuciones;", x.groupby("modo").size().to_dict())
def resumen(g):
w = g["efectivo"]
return pd.Series({c: np.average(g[c], weights=w)
for c in ("total", "retraso", "desliz")}
| {"efectivo_musd": w.sum() / 1e6,
"comisiones": g["comision"].sum()})
x.groupby("modo").apply(resumen, include_groups=False).round(2)531 ejecuciones; {'paper': 183, 'real': 348}
total retraso desliz efectivo_musd comisiones
modo
paper -0.04 0.27 -0.31 5.21 303.30
real 0.97 -0.77 1.74 11.70 593.71Ponderadas por efectivo, las ejecuciones de paper salen 0,04 pb mejor que el backtest y las reales 0,97 pb peor. Pero mirar solo esa columna engaña: en la cuenta real el retraso aporta −0,77 pb (el mercado se movió a favor entre la señal y el envío, por azar) y el deslizamiento de ejecución es de 1,74 pb. En paper, el deslizamiento es de −0,31 pb: el broker de paper no cobra nada por ejecutar. Las órdenes al cierre tienen un deslizamiento sin sentido frente al medio al enviar (se ejecutan diez minutos después, en la subasta), así que en adelante el deslizamiento se mide solo con las órdenes a mercado.
Lo que simula un broker de paper
El simulador de paper de los datos llena las órdenes a mercado al precio medio del instante de la ejecución, sin horquilla y sin deslizamiento extra; la cuenta real paga media horquilla más un extra que depende del momento. Es una simplificación, pero no una caricatura: los brokers lo documentan. Alpaca dice que su paper trading no tiene en cuenta el impacto de las órdenes, la filtración de información, el deslizamiento por latencia, la posición en la cola de las límite no ejecutables, la mejora de precio, las tasas regulatorias ni los dividendos, y que la cantidad de la orden no se compara con la del mejor precio1Nota 1 · Alpaca, «Paper Trading» (consultado el 2026-10-22; enlace en los recursos).. Interactive Brokers advierte que las ejecuciones de su cuenta paper pueden caer o no dentro del mercado del momento y que no son indicativas de lo que pasaría en el real2Nota 2 · Interactive Brokers, «I Received an Execution in my Paper Trader Account that Did Not Correspond to the Market at the Time, What Happened?», actualizado el 11 de noviembre de 2024 (consultado el 2026-10-22; enlace en los recursos)..
mk = x[x["tipo"] == "mercado"].copy()
mk["momento"] = np.where(
mk["motivo"] == "resto_de_limite", "resto_limite",
np.where(mk["ts_envio"].dt.hour < 17, "apertura", "cierre"))
def media_et(g):
return pd.Series({"n": len(g), "media": g.mean(),
"desv": g.std(),
"et": g.std() / np.sqrt(len(g))})
med = (mk.groupby(["modo", "momento"])["desliz"]
.apply(media_et).unstack())
med.round(2) n media desv et
modo momento
paper cierre 108.0 0.02 0.56 0.05
resto_limite 21.0 0.14 0.43 0.09
real apertura 31.0 5.22 3.73 0.67
cierre 141.0 1.49 1.55 0.13
resto_limite 48.0 2.32 2.40 0.35En paper, el deslizamiento medio frente al medio al enviar es prácticamente cero (0,02 pb al cierre, 0,14 pb en los restos de las límite): lo único que queda es lo que se mueve el medio en los milisegundos entre el envío y la ejecución. En real hay tres momentos: las órdenes del cierre (10 minutos antes), los restos de las límite que no se llenaron (tres minutos antes del cierre) y las de los lunes que se ejecutan al abrir el martes. Abrir cuesta 5,2 pb, más del triple que cerrar (1,5 pb). Y cada media lleva su error típico: con 31 ejecuciones de apertura, ±0,67 pb; con 141 de cierre, ±0,13 pb.
La latencia es la otra mitad de la promesa del paper. Desde que el OMS envía hasta que recibe la aceptación, y desde la aceptación hasta la primera ejecución en el broker:
ac = ev[ev["evento"] == "aceptada"].merge(
ordn[["id_orden_cliente", "ts_envio", "tipo"]])
ac["ms_acuse"] = (pd.to_datetime(ac["ts_recibido"])
- ac["ts_envio"]).dt.total_seconds() * 1e3
prim = (ej.groupby("id_orden_cliente")["ts_broker"].min()
.rename("primera"))
ac = ac.join(prim, on="id_orden_cliente")
ac["ms_ejec"] = (pd.to_datetime(ac["primera"])
- pd.to_datetime(ac["ts_broker"])
).dt.total_seconds() * 1e3
q = lambda s: s.quantile([0.1, 0.5, 0.9]).round(1).tolist()
corte = ac["ms_acuse"] > 10_000 # acuse recibido tras un corte
print("acuses tras un corte:", ac[corte].groupby("modo").size()
.to_dict())
for modo, g in ac[~corte].groupby("modo"):
print(f"{modo:5s} acuse p10/p50/p90 ms:", q(g["ms_acuse"]),
"| 1.ª ejecución a mercado:",
q(g.loc[g["tipo"] == "mercado", "ms_ejec"]))acuses tras un corte: {'real': 42}
paper acuse p10/p50/p90 ms: [24.0, 31.3, 40.7] | 1.ª ejecución a mercado: [2.9, 5.0, 10.2]
real acuse p10/p50/p90 ms: [69.8, 87.3, 110.0] | 1.ª ejecución a mercado: [19.4, 61.9, 167.9]Antes, una exclusión: 42 acuses de la cuenta real llegaron más de 10 segundos después del envío. No es latencia sino los cortes de conexión de la lección 4.5.2: las órdenes enviadas justo antes de un corte reciben su acuse al reconectar, minutos después. Sin ellos, la mediana del acuse es de 31 ms en paper y de 87 ms en real; la primera ejecución de una orden a mercado llega en 5 ms en paper y en 62 ms en real, con un percentil 90 de 168 ms. Para un sistema que opera al cierre, unas decenas de milisegundos no cambian nada; para uno que reacciona a cada cotización, el paper falsea la parte más importante.
Recuperar los parámetros plantados
Los datos tienen la respuesta conocida: el precio de cada ejecución real a mercado es el medio por
, con la media horquilla del ETF y un extra normal cuya media y
desviación dependen del momento (vivo4_deslizamiento_verdad.csv). Lo medido arriba es más el
movimiento del medio durante la latencia. Para llegar a hace falta , y los datos no la dan
directamente. Se puede estimar con la regla de las órdenes límite del sistema, que se colocan a un cuarto de horquilla
del medio (media horquilla = 2 × la distancia del límite al medio):
lim = ordn[ordn["tipo"] == "limite"]
s_l = np.where(lim["lado"] == "compra", 1, -1)
dist = s_l * (lim["precio_medio_envio"] - lim["precio_limite"]) \
/ lim["precio_medio_envio"] * 1e4
h = (2 * dist).groupby(lim["instrumento"]).agg(["mean", "count"])
print(h.round(2).T.to_string())
mk["h"] = np.where(mk["modo"] == "real",
mk["instrumento"].map(h["mean"]), 0.0)instrumento ORAU ORCM OREM OREU ORIG ORRE ORSC ORTL ORTN ORUS
mean 0.67 1.59 1.6 0.88 0.36 0.93 0.49 0.72 0.33 0.19
count 13.00 6.00 9.0 12.00 13.00 6.00 17.00 9.00 14.00 12.00Las medias horquillas estimadas van de 0,2 pb (ORUS, el ETF de la renta variable de EE. UU.) a 1,6 pb (OREM y ORCM, emergentes y materias primas). Son estimaciones ruidosas: el precio límite se redondea al céntimo, y en un ETF de 24 USD un céntimo son 4 pb, diez veces la distancia que se quiere medir; con seis órdenes, el error de ORCM es grande. En un broker real, la horquilla se mide con las cotizaciones (lección 1.1.5); aquí la regla de las límites basta.
verdad = pd.read_csv("vivo4_deslizamiento_verdad.csv")
mk["extra"] = mk["desliz"] - mk["h"]
est = (mk.groupby(["modo", "momento"])["extra"]
.apply(media_et).unstack()[["n", "media", "et"]])
comp = est.join(verdad.set_index(["modo", "momento"]))
comp["z"] = (comp["media"] - comp["media_pb"]) / comp["et"]
comp.round(2) n media et media_pb desviacion_pb z
modo momento
paper cierre 108.0 0.02 0.05 0.0 0.0 0.31
resto_limite 21.0 0.14 0.09 0.0 0.0 1.49
real apertura 31.0 4.44 0.69 3.0 4.0 2.08
cierre 141.0 0.85 0.13 1.0 1.5 -1.20
resto_limite 48.0 1.61 0.34 1.5 2.0 0.32La figura de la cabecera lo resume. Al cierre, el extra medido es de 0,85 ± 0,13 pb frente al 1,0 verdadero; en los restos de las límite, 1,61 ± 0,34 frente a 1,5. Los dos están a menos de 1,2 errores típicos de la verdad. En la apertura, 4,44 ± 0,69 frente a 3,0: a algo más de dos errores típicos, y con 31 ejecuciones de unos pocos lunes; es el tipo de diferencia que en datos reales no se sabría si es azar o un cambio de régimen. El paper da exactamente 0: es su regla. Y la columna de paper deja claro lo que no se puede hacer: calibrar el modelo de costes con el paper.
Las diferencias entre momentos se pueden medir sin conocer la horquilla. Con un efecto fijo por ETF, la horquilla de cada uno queda absorbida y la regresión da cuánto más cuesta la apertura o el resto que el cierre. Las varianzas son distintas por momento (casi 4 pb de desviación en la apertura, 1,5 al cierre), así que los errores tienen que ser robustos:
real = mk[mk["modo"] == "real"]
fit = smf.ols("desliz ~ C(instrumento) + "
"C(momento, Treatment('cierre'))", data=real
).fit(cov_type="HC1")
fila = fit.params.filter(like="momento")
tabla = pd.DataFrame({"coef": fila,
"et": fit.bse.filter(like="momento")})
tabla.index = ["apertura - cierre", "resto - cierre"]
tabla["verdad"] = [3.0 - 1.0, 1.5 - 1.0]
tabla.round(2) coef et verdad
apertura - cierre 3.64 0.70 2.0
resto - cierre 0.87 0.35 0.5La apertura cuesta unos 3,6 pb más que el cierre según la regresión, frente a 2,0 de verdad: el mismo exceso de las aperturas de la tabla anterior, ahora con un error típico robusto de unos 0,7 pb. El resto cuesta 0,9 pb más que el cierre, frente a 0,5. Con efectos fijos no hay nivel: la regresión no puede decir cuánto cuesta el cierre, solo cuánto más cuesta lo demás. Para el nivel hace falta la horquilla.
Un libro en sombra
Comparar el paper de septiembre con el real de noviembre mezcla dos cosas: la regla de ejecución y el mercado de cada periodo. El shadow trading (operar en sombra) las separa: el mismo sistema lleva, junto al libro real, un libro que aplica otra regla de ejecución a las mismas órdenes, los mismos días. Aquí la sombra es la regla del paper aplicada a las órdenes reales: cada ejecución a mercado al medio del envío, las límite a su límite y las de cierre al cierre. Es lo que habría dicho la cuenta paper si hubiera seguido en paralelo.
Fuente Mermaid
flowchart TB S[Señal] --> O[OMS] O --> BR[Broker real] O --> SB[Libro en sombra] BT[Backtest] --> C[Comparar] BR --> C SB --> C
Una variante habitual es la contraria: correr la estrategia nueva en sombra, sin enviar órdenes, junto a la que ya opera, con los mismos datos y el mismo reloj, y comparar sus señales y sus ejecuciones supuestas antes de darle dinero. La arquitectura de la lección 4.5.1 lo permite sin cambios: otra señal y otro OMS suscritos al mismo bus, con un broker simulado.
r = x[x["modo"] == "real"].copy()
r["px_sombra"] = np.where(r["tipo"] == "mercado",
r["precio_medio_envio"], r["precio_ultimo"])
usd = lambda px: r["s"] * r["cantidad_ultima"] * (
px - r["precio_backtest"])
r["coste_real"] = usd(r["precio_ultimo"]) + r["comision"]
r["coste_sombra"] = usd(r["px_sombra"]) + r["comision"]
tot = r[["coste_real", "coste_sombra"]].sum()
print(tot.round(2).to_dict())
oculto = tot["coste_real"] - tot["coste_sombra"]
efec_mk = r.loc[r["tipo"] == "mercado", "efectivo"].sum()
print(f"Lo que el paper esconde: {oculto:,.2f} USD, "
f"{oculto / efec_mk * 1e4:.2f} pb de lo ejecutado a mercado")
import exchange_calendars as xcals
dias = len(xcals.get_calendar("XNYS").sessions_in_range(
"2025-10-13", "2025-12-31"))
print(f"{dias} sesiones; al año, {oculto / dias * 252:,.0f} USD, "
f"un {oculto / dias * 252 / 1e6:.2%} del capital"){'coste_real': 1732.02, 'coste_sombra': 27.02}
Lo que el paper esconde: 1,705.00 USD, 2.49 pb de lo ejecutado a mercado
56 sesiones; al año, 7,672 USD, un 0.77% del capitalEn las 56 sesiones de la cuenta real, el libro real cuesta 1.732 USD frente al backtest (deslizamiento y comisiones) y la sombra, 27 USD: las comisiones casi compensadas por el retraso, que esos días jugó a favor. La diferencia, 1.705 USD, es lo que el paper esconde: el deslizamiento de las órdenes a mercado, 2,49 pb de lo que se ejecutó a mercado. Llevado a un año, unos 7.700 USD, un 0,77 % del capital, para una estrategia que espera unos pocos puntos al año. La figura muestra el coste acumulado frente al backtest de los dos periodos. En paper, la curva sube y baja con el retraso (cientos de dólares en un día) y termina en 283 USD, casi solo comisiones. En real, la curva real y la sombra suben y bajan juntas, porque el retraso es el mismo para las dos, pero la distancia entre ellas crece casi sin interrupción: es el deslizamiento, que se paga en cada orden. Ningún mes de paper habría anticipado esa distancia.
Seguimiento del slippage en vivo
Medir el deslizamiento al final del trimestre no basta: hay que saber pronto si el modelo de costes está mal. La herramienta clásica es un CUSUM de Page: acumula las desviaciones del deslizamiento observado frente al que dice el modelo, menos una holgura, y avisa cuando la suma pasa un umbral.
con el deslizamiento de la ejecución , el del modelo, la desviación esperada (1,5 pb al cierre), (detecta desvíos de un sigma) y (pocas falsas alarmas). Se aplica a las ejecuciones de cierre de la cuenta real, en orden, con tres modelos de coste: el del paper (0), solo la media horquilla y la media horquilla más 1 pb.
def cusum(d, modelo, sigma=1.5, k=0.5, h=5.0):
s, camino = 0.0, []
for v in (d - modelo) / sigma:
s = max(0.0, s + v - k)
camino.append(s)
camino = np.array(camino)
salta = np.argmax(camino > h) if (camino > h).any() else None
return camino, salta
cie = mk[(mk["modo"] == "real") & (mk["momento"] == "cierre")] \
.sort_values("ts_broker")
modelos = {"paper (0)": 0.0, "media horquilla": cie["h"],
"horquilla + 1 pb": cie["h"] + 1.0}
for nombre, mod in modelos.items():
camino, i = cusum(cie["desliz"], mod)
cuando = ("nunca" if i is None else
f"ejecución {i + 1}, {cie['ts_broker'].iloc[i][:10]}")
print(f"{nombre:17s} alarma: {cuando}")
ape = mk[(mk["modo"] == "real") & (mk["momento"] == "apertura")] \
.sort_values("ts_broker")
_, i = cusum(ape["desliz"], ape["h"] + 1.0)
print("aperturas con el modelo del cierre: alarma en la ejecución",
i + 1, ape["ts_broker"].iloc[i][:10])paper (0) alarma: ejecución 8, 2025-10-21
media horquilla alarma: ejecución 9, 2025-10-21
horquilla + 1 pb alarma: nunca
aperturas con el modelo del cierre: alarma en la ejecución 2 2025-10-14Con el modelo del paper, la alarma salta en la octava ejecución de cierre, el 21 de octubre: seis sesiones después de empezar en real. Con solo la media horquilla, una ejecución más tarde: el extra de 1 pb es pequeño pero constante. Con horquilla más 1 pb no salta nunca en once semanas: es el modelo bueno. Si se aplicase el modelo del cierre a las aperturas, saltaría en la segunda ejecución: los modelos de coste se calibran y se vigilan por momento y tipo de orden, no en bloque.
Fuente Mermaid
flowchart TB
E[Ejecución] --> D[Deslizamiento]
D --> C[CUSUM por momento]
C --> A{Pasa el umbral}
A -- no --> E
A -- sí --> AL[Alerta]
AL --> R[Recalibrar modelo]La alerta no corrige nada sola: dice que el modelo de costes con el que se aprobó la estrategia ya no describe la realidad. Lo siguiente es recalibrarlo con las ejecuciones reales y repetir el backtest con él (lección 2.4.7); si la estrategia ya no compensa sus costes, se reduce o se para (lección 4.2.5). El envío de la alerta es la lección 4.5.4.
Lo que el paper sí comprueba
Nada de lo anterior hace inútil el paper trading. Las seis semanas de paper de los datos sirvieron para comprobar que las señales en vivo coinciden con las del backtest (lección 4.5.1), que la máquina de estados aguanta los mensajes reales del broker y una desconexión (lección 4.5.2), que los rechazos se tratan bien y que el sistema arranca y para cada día sin intervención. Para lo que no sirvieron es para medir costes. La forma práctica de unirlo: paper para la operativa, una cuenta real pequeña para los costes (con la sombra en paralelo), y el tamaño completo solo cuando el CUSUM lleve semanas sin saltar con el modelo con el que se hizo el backtest.
Ejercicios
- Repite la descomposición del primer apartado solo con las órdenes a mercado del cierre. ¿Cuánto del 1,74 pb de deslizamiento real explica la media horquilla estimada?
Solución
c = mk[mk["momento"] == "cierre"]
for modo, g in c.groupby("modo"):
w = g["efectivo"]
print(modo, "deslizamiento", round(np.average(g["desliz"],
weights=w), 2),
"| media horquilla", round(np.average(g["h"], weights=w), 2),
"| retraso", round(np.average(g["retraso"], weights=w), 2))paper deslizamiento 0.03 | media horquilla 0.0 | retraso 1.67
real deslizamiento 1.4 | media horquilla 0.55 | retraso -0.43Al cierre, la cuenta real paga 1,4 pb ponderado por efectivo, de los que la media horquilla estimada explica 0,55: el resto es el extra. En paper, 0,03. El retraso, en cambio, cambia de signo entre periodos (+1,67 pb en paper, −0,43 en real): es el ruido del mercado en cinco minutos, no un coste, y cuatro meses no bastan para que su media se acerque a cero.
- ¿Depende el deslizamiento real a mercado del ETF? Compara el deslizamiento medio por ETF con su media horquilla estimada.
Solución
por = (mk[mk["modo"] == "real"].groupby("instrumento")
.agg(n=("desliz", "size"), desliz=("desliz", "mean"),
h=("h", "first")))
por["desliz - h"] = por["desliz"] - por["h"]
print(por.round(2).sort_values("h").to_string())
print("correlación desliz-h:", round(por["desliz"].corr(por["h"]), 2)) n desliz h desliz - h
instrumento
ORUS 20 1.12 0.19 0.92
ORTN 25 2.85 0.33 2.53
ORIG 34 1.79 0.36 1.43
ORSC 28 2.10 0.49 1.61
ORAU 22 2.22 0.67 1.55
ORTL 27 1.36 0.72 0.64
OREU 19 3.58 0.88 2.70
ORRE 18 2.26 0.93 1.32
ORCM 9 3.04 1.59 1.45
OREM 18 2.68 1.60 1.08
correlación desliz-h: 0.53Los ETF con más horquilla deslizan algo más (correlación de 0,53 entre las diez medias), pero lo que queda tras restar la media horquilla va de 0,6 a 2,7 pb. Con unas 20 ejecuciones por ETF, mezclando momentos (cada ETF tiene una proporción distinta de aperturas, que cuestan el triple) y horquillas estimadas con pocas órdenes, cada media tiene un error de medio punto o más: los datos no permiten distinguir un extra por ETF, y en la verdad plantada no lo hay, solo depende del momento. En datos reales, el extra también depende del tamaño de la orden frente a la liquidez (impacto, lección 4.3.1); aquí las órdenes son pequeñas frente al volumen de los ETF y ese efecto no está.
- Una estrategia espera 3 pb de beneficio bruto por operación y opera al cierre con órdenes a mercado. Con lo medido en la cuenta real, ¿cuánto queda? ¿Y si la mitad de las operaciones se hicieran en la apertura?
Solución
Al cierre, el deslizamiento real medio es de 1,5 pb por ejecución y las comisiones añaden algo más (0,0035 USD por acción: 0,35 pb en una acción de 100 USD): quedan unos 1,1 pb, poco más de un tercio de lo esperado. Si la mitad se ejecuta en la apertura (5,2 pb), el deslizamiento medio sube a unos 3,4 pb y la estrategia pierde dinero antes de comisiones. El paper habría dicho 3 pb limpios en los dos casos.
Footnotes
-
Alpaca, «Paper Trading» (consultado el 2026-10-22; enlace en los recursos). ↩
-
Interactive Brokers, «I Received an Execution in my Paper Trader Account that Did Not Correspond to the Market at the Time, What Happened?», actualizado el 11 de noviembre de 2024 (consultado el 2026-10-22; enlace en los recursos). ↩
Respuesta plegada en cada pregunta
Autoevaluación
¿Por qué las ejecuciones a mercado de la cuenta paper de los datos salen a 0,02 pb del medio y las de la cuenta real a 1,49 pb?
- APorque el broker de paper llena al precio medio, sin horquilla ni deslizamiento; la cuenta real paga media horquilla más un extra
- BPorque en paper el sistema operó mejor
- CPorque la cuenta real tenía más capital
- DPor azar: con más datos serían iguales
Ver respuesta
Respuesta A. Porque el broker de paper llena al precio medio, sin horquilla ni deslizamiento; la cuenta real paga media horquilla más un extra
Es la regla del simulador de paper de los datos, y la de muchos brokers en alguna medida: Alpaca, por ejemplo, documenta que su paper no tiene en cuenta el impacto, el deslizamiento por latencia ni la posición en la cola. El 0,02 que queda en paper es el movimiento del medio en los milisegundos de latencia.
La cuenta real ejecuta 0,97 pb peor que el backtest. ¿Por qué no basta esa cifra para calibrar el modelo de costes?
- APorque está en puntos básicos
- BPorque el backtest no tiene comisiones
- CSí basta
- DPorque mezcla el retraso (el medio se mueve 5 minutos entre la señal y el envío, con media cero pero mucho ruido) con el deslizamiento de ejecución
Ver respuesta
Respuesta D. Porque mezcla el retraso (el medio se mueve 5 minutos entre la señal y el envío, con media cero pero mucho ruido) con el deslizamiento de ejecución
Frente al medio al enviar, la cuenta real paga 1,74 pb; frente al backtest, 0,97 pb, porque el retraso aporta −0,77 pb de puro ruido en estos cuatro meses. El coste de ejecución se mide frente al medio en el momento de enviar; el retraso, aparte.
¿Qué recupera la regresión del deslizamiento sobre efectos fijos de ETF y del momento, sin conocer las horquillas?
- ALas horquillas
- BLas diferencias de deslizamiento entre momentos (apertura frente a cierre), no su nivel
- CEl deslizamiento total
- DNada
Ver respuesta
Respuesta B. Las diferencias de deslizamiento entre momentos (apertura frente a cierre), no su nivel
Con un efecto fijo por ETF, la horquilla de cada ETF y el nivel común se confunden: solo se identifican las diferencias. Para el nivel hace falta la horquilla, que en la lección se estima con las órdenes límite.
Un CUSUM vigila el deslizamiento al cierre frente a un modelo calibrado con el paper (coste esperado 0). ¿Cuándo salta?
- ANunca
- BAl final de diciembre
- CEn la octava ejecución al cierre de la cuenta real, el 21 de octubre, a la semana de empezar
- DAntes de empezar
Ver respuesta
Respuesta C. En la octava ejecución al cierre de la cuenta real, el 21 de octubre, a la semana de empezar
El coste real al cierre es de 1,5 pb por ejecución y el modelo dice 0: la suma acumulada pasa el umbral en ocho ejecuciones. Con un modelo de horquilla más 1 pb no salta nunca: está bien calibrado.
¿Qué aporta un libro en sombra frente a comparar el paper de septiembre con el real de noviembre?
- ANada
- BMás rentabilidad
- CEvita pagar comisiones
- DCompara las mismas órdenes, los mismos días y los mismos precios con dos reglas de ejecución: la diferencia es solo la regla, no el mercado
Ver respuesta
Respuesta D. Compara las mismas órdenes, los mismos días y los mismos precios con dos reglas de ejecución: la diferencia es solo la regla, no el mercado
Paper y real en periodos distintos mezclan la regla de ejecución con el mercado de cada periodo. La sombra aplica la regla del paper a las órdenes reales: en la lección, la diferencia son 1.705 USD en once semanas, 2,49 pb sobre lo ejecutado a mercado.
Ideas clave
- El paper trading comprueba que el sistema funciona (señales, órdenes, estados, reconexiones); no mide costes. Su broker suele llenar al medio, sin cola ni impacto.
- La diferencia entre backtest y real se separa en retraso (ruido de media cero si la señal no tiene información de corto plazo) y deslizamiento de ejecución (el coste que se puede modelar).
- El deslizamiento depende del momento: en los datos, abrir a mercado el lunes cuesta unas tres veces más que ejecutar al cierre. Un modelo único se equivoca en los dos.
- Cada media lleva su error típico: con 31 ejecuciones, el deslizamiento de apertura se mide con ±0,7 pb; con 141, el de cierre con ±0,13.
- Un libro en sombra aplica otra regla de ejecución a las mismas órdenes y aísla lo que cuesta la regla.
- El seguimiento en vivo es un contraste secuencial (CUSUM) del deslizamiento frente al modelo, por momento: salta en días si el modelo es el del paper.