Me mandaste el notebook el domingo por la noche y lo tengo abierto en el segundo monitor desde entonces. Momentum transversal, los 150 perps Binance USDⓈ-M con mayor volumen en dólares a 30 días, rebalanceo semanal, largos en el decil superior y cortos en el inferior, de 2021-01 a 2026-06. Sharpe 2.31, drawdown máximo del 14.2% y un modelo de costes que me pareció honesto: taker al entrar y al salir, funding acumulado por intervalo y un término de slippage que escala según tu tamaño frente al libro. Hiciste bien las partes difíciles. Luego preguntaste por qué seis semanas de paper trading han salido planas y si el edge se había deteriorado.
No se deterioró. Nunca estuvo en el backtest. Mira la celda 4:
info = requests.get(BASE + "/fapi/v1/exchangeInfo").json()
symbols = [s["symbol"] for s in info["symbols"]
if s["status"] == "TRADING" and s["quoteAsset"] == "USDT"]
Llamaste a ese endpoint en junio de 2026 y usaste la respuesta para definir qué se podía operar en marzo de 2021. Por construcción, todos los símbolos de esa lista habían sobrevivido hasta junio de 2026. Ese es todo el fallo, y pesa más que el resto de tu modelo de costes junto.
Lo que el endpoint no te dirá
exchangeInfo no tiene historial. No hay parámetro asOf, ni archivo ni registro de cambios. Es una foto del momento actual, y Binance nunca te ha prometido nada más. Cuando se retira un contrato de cotización, su entrada se elimina de la respuesta y, para la API, el ticker deja de existir. Binance ha listado más de 600 perps USDⓈ-M desde 2019 y ha retirado bastante más de cien. BTS, COCOS, TOMO, RAY, FTT, SC y una larga cola de contratos de altcoins de la temporada alcista de 2021 que tuvieron un trimestre glorioso y luego se fueron quedando sin actividad hasta que el exchange los retiró.
Piensa qué nombres atraen a un filtro de momentum a 30 días. No BTC. Le atrae lo que acaba de triplicarse con el subidón de una cotización y un ciclo de Twitter. Esa población coincide en gran medida con la de los futuros retiros, y el filtro de universo eliminó precisamente esa coincidencia.
Volví a ejecutar tu estrategia con nuestro archivo de instantáneas, un universo puntual, la misma señal y los mismos costes, y el Sharpe quedó en 0.74 con un drawdown del 31%. Aproximadamente dos quintas partes de la diferencia se deben a símbolos retirados que nunca debiste poder mantener. Otro cuarto se debe al problema inverso, que es más sutil y sospecho que te gustará menos.
El otro extremo de la máquina del tiempo: símbolos que aún no existían
Tu feature mide el retorno a 90 días. Tu ventana móvil tiene min_periods=20, porque la configuraste una vez para que el calentamiento no arrasara el primer trimestre de la muestra y nunca volviste a revisarla. Así, un contrato que se incorporó hace 21 días recibe una puntuación de momentum calculada con tres semanas de cotización posterior al listado, queda en el decil superior más o menos cuando el listado sale bien y entra en tu cartera.
Esa parte es, en principio, legítima. Esto es lo que no lo es: tu proveedor rellenó retroactivamente algunos de esos símbolos con datos spot o de índice de antes de que existiera el perp, así que varios contratos tienen historial de klines anterior a su propio onboardDate. Puedes comprobarlo en aproximadamente un minuto. Cruza tu tabla de precios con las fechas de incorporación y cuenta las filas anteriores al listado. En tus datos hay 41 símbolos con barras previas al listado; uno de ellos, en el verano de 2023, aporta una ganancia de una sola semana del 6% a la curva de capital gracias a una posición en un contrato que no existiría hasta dentro de otros nueve días.
Una cadena de ticker no es un identificador. Es una etiqueta que el exchange te alquila, a veces dos veces.
Eso nos lleva a los cambios de nombre. MATICUSDT pasó a ser POLUSDT. FTMUSDT pasó a ser SUSDT en una conversión 1:1. El caso de LUNA en mayo de 2022 dejó un LUNCUSDT y, más tarde, un LUNAUSDT nuevo que comparte la raíz del ticker con algo que perdió prácticamente todo su valor. Si tu cargador usa la cadena del símbolo como clave y concatena todos los archivos que encuentra, tienes al menos una serie con una discontinuidad que no es un movimiento de precio, y tu feature de momentum interpretará esa discontinuidad como la señal más fuerte del corte transversal.
Todo lo demás que cambia bajo tus pies
Una vez aceptas que la lista de símbolos varía con el tiempo, el mismo argumento se aplica a todos los demás campos de esa respuesta. Estás usando sus valores de hoy para toda la historia.
| Campo | Cómo cambia | Qué falla |
|---|---|---|
| status | TRADING → SETTLING → desaparecido | Sesgo de supervivencia; salidas fantasma a un precio de cierre al que nunca se operó |
| onboardDate | Nuevos listados cada semana; ausente en símbolos retirados | Operar contratos antes de que existieran |
| tickSize / stepSize | Se reajustan cuando cambian los niveles de precio | Redondeo de órdenes y precios límite que habrían sido rechazados |
| minNotional | Aumenta con el tiempo en libros poco líquidos | Tramos pequeños que tu router en vivo rechazaría |
| fundingIntervalHours | 8h durante años; luego 4h o 1h para muchos símbolos | Carry desviado por un factor de 2–3× justo en las altcoins que mantiene tu filtro |
| leverage brackets | Se revisan los tramos y el margen de mantenimiento | Modelización de liquidaciones y capacidad de margen |
El funding es lo que más te perjudica en este caso. Tu bucle de acumulación supone tres pagos al día durante todo el historial. Una parte importante de la cartera de altcoins pasó a funding cada cuatro horas, y los tramos cortos en símbolos con funding alto explican gran parte de tu PnL simulado. No te equivocas por un error de redondeo. Te equivocas por un factor.
El retiro de cotización es un evento, y no lo estás modelando
En la nueva ejecución puntual le concedí a la estrategia una salida generosa. Los retiros reales siguen un guion: un anuncio, normalmente con siete a catorce días de antelación; luego, un periodo de solo reducción; y finalmente, liquidación forzosa a un precio de marca. El anuncio es información pública y puedes actuar en consecuencia, así que la simulación honesta consiste en salir al cierre del día del anuncio. Pero ese cierre ya está un 10-20% por debajo de la semana anterior en un retiro típico de una altcoin, el libro tiene poca liquidez y tu término de slippage debe reconocer que opera en un régimen distinto. Si tu motor de ejecución te da el precio de liquidación sin impacto, has hecho que los activos moribundos se puedan operar silenciosamente a valor razonable.
Si nunca archivaste exchangeInfo, aún hay solución. El volcado público en data.binance.vision/data/futures/um/monthly/klines/ sigue conservando directorios de símbolos retirados mucho después de que la API los haya olvidado. Enumera los directorios y, para cada símbolo, el primer y el último archivo mensual te darán una ventana aproximada de listado y retiro sin necesidad de un proveedor. Es una reconstrucción, no un registro, y no recuperará los tamaños de tick ni los intervalos de funding. Pero te dirá qué existía y cuándo, que es el 80% de lo que necesitas para el lunes.
Qué te recomiendo construir antes de volver a tocar la señal
- Un cron que descargue a diario exchangeInfo de cada exchange que investigues y lo escriba en almacenamiento de objetos con la fecha como clave. Comprimido con gzip ocupa menos de medio megabyte. Diez años te costarán un error de redondeo en S3 y te darán una capacidad de investigación que no podrás comprar más adelante.
- Un maestro de activos derivado de esas instantáneas: una fila por (venue, symbol, valid_from, valid_to) con el conjunto completo de campos. Genera el maestro comparando instantáneas consecutivas y trata cualquier cambio de campo como una fila nueva.
- Una función de universo que exija un argumento de marca de tiempo.
universe(ts), nuncauniverse(). Haz imposible usarla de otro modo para que nadie, ni siquiera tú en el futuro a la 1am, recurra por accidente a la lista de supervivientes. - Una aserción previa al listado en el cargador de datos: no puede haber barras anteriores a
onboard_tsmenos un día. Si falla, detén la ejecución; no muestres una advertencia. - Un ID interno estable para cada instrumento que sobreviva a los cambios de nombre, y deja el ticker como atributo secundario. Asigna POL y MATIC al mismo ID y marca las redenominaciones para que la lógica de continuidad pueda rechazar uniones entre ellas.
Hazlo y vuelve a ejecutar. Mi apuesta es que te acercarás al 0.74 que obtuve, y la pregunta interesante será si un 0.74 que incluye los símbolos desaparecidos tiene algo que merezca probarse en paper trading. Puede que sí. El momentum transversal en perps tiene su valor, y parte de lo que queda tras corregir el universo es carry genuino del lado corto. También verás que tus resultados de paper trading y tu backtest empiezan a coincidir, porque el paper trading siempre ha usado un universo puntual. Nunca tuvo alternativa.
P. D. Esto no es una rareza de las criptomonedas; aquí simplemente se nota más. Los investigadores de renta variable llevan lidiando con los retornos de activos retirados y los tickers reciclados desde que CRSP empezó a distribuir esos datos, y los mercados de predicción llevan el problema al extremo: todos los contratos vencen por diseño, así que el universo no es más que listados y vencimientos. Si alguna vez adaptas este filtro a Kalshi, crea primero el maestro de activos. Allí no hay otro tipo de historial.
← Todos los artículos


