Você me enviou o notebook no domingo à noite, e desde então deixei aberto no segundo monitor. Momentum cross-sectional, os 150 maiores perpétuos USDⓈ-M da Binance por volume em dólares nos últimos 30 dias, rebalanceamento semanal, comprado no decil superior e vendido no inferior, de 2021-01 a 2026-06. Sharpe 2.31, drawdown máximo de 14.2% e um modelo de custos que me pareceu honesto: taker na entrada, taker na saída, funding acumulado por intervalo e um termo de slippage que escala com o tamanho da sua ordem em relação ao livro. Você acertou as partes difíceis. Aí perguntou por que seis semanas de paper trading deram resultado estável e se o edge tinha se desgastado.
Não se desgastou. Ele nunca esteve no backtest. Veja a célula 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"]
Você chamou esse endpoint em junho de 2026 e usou a resposta para definir o que era negociável em março de 2021. Por definição, todos os símbolos daquela lista sobreviveram até junho de 2026. Esse é o problema inteiro, e ele vale mais do que o restante do seu modelo de custos somado.
O que o endpoint não conta
exchangeInfo não tem histórico. Não há parâmetro asOf, arquivo nem changelog. É uma fotografia de agora, e a Binance nunca prometeu nada além disso. Quando um contrato é deslistado, sua entrada é removida da resposta e, para a API, o ticker deixa de existir. Desde 2019, a Binance listou mais de 600 perpétuos USDⓈ-M e retirou bem mais de uma centena. BTS, COCOS, TOMO, RAY, FTT, SC e uma longa cauda de contratos da altseason de 2021, que tiveram um trimestre glorioso e depois perderam liquidez até a plataforma removê-los.
Pense em quais nomes um filtro de momentum de 30 dias adora. Não é BTC. Ele adora aquilo que acabou de triplicar numa alta de listagem e num ciclo do Twitter. Os grupos de ativos que esse filtro favorece e os que acabam deslistados se sobrepõem bastante, e o filtro do seu universo removeu essa sobreposição.
Refiz sua execução com nosso arquivo de snapshots, universo point-in-time, mesmo sinal e mesmos custos. O Sharpe ficou em 0.74, com drawdown de 31%. Cerca de dois quintos da diferença vêm de símbolos deslistados que você nunca poderia ter mantido. Outro quarto vem do problema oposto, que é mais sutil e que, suspeito, vai agradar menos.
O outro lado da máquina do tempo: símbolos que ainda não existiam
Seu indicador usa retorno de 90 dias. Sua janela móvel tem min_periods=20, porque você definiu esse valor uma vez para evitar que o aquecimento eliminasse o primeiro trimestre da amostra e nunca mais o revisou. Assim, um contrato listado há 21 dias recebe uma pontuação de momentum calculada com três semanas de variação de preço após a listagem, fica no decil superior sempre que a listagem vai bem e entra na sua carteira.
Dá para argumentar que essa parte é legítima. O que não é: seu provedor preencheu retroativamente alguns desses símbolos com dados de spot ou de índice anteriores à existência do perpétuo. Por isso, alguns contratos têm histórico de klines anterior ao próprio onboardDate. Dá para verificar isso em cerca de um minuto. Cruze sua tabela de preços com as datas de listagem e conte as linhas anteriores à listagem. Nos seus dados, há 41 símbolos com candles anteriores à listagem, e um deles, no verão de 2023, acrescenta um ganho de 6% em uma única semana à curva de patrimônio, vindo de uma posição em um contrato que só existiria nove dias depois.
Uma string de ticker não é um identificador. É um rótulo que a exchange aluga, às vezes duas vezes.
Isso nos leva às renomeações. MATICUSDT virou POLUSDT. FTMUSDT virou SUSDT numa conversão de 1:1. A situação da LUNA em maio de 2022 deixou uma LUNCUSDT e, depois, uma nova LUNAUSDT, que compartilha a raiz do ticker com algo que perdeu praticamente todo o valor. Se seu loader usa a string do símbolo como chave e concatena todos os arquivos que encontra, há pelo menos uma série com uma descontinuidade que não é movimento de preço, e seu indicador de momentum vai interpretar essa descontinuidade como o sinal mais forte do cross-section.
Todo o resto que muda sem você perceber
Quando você aceita que a lista de símbolos varia com o tempo, o mesmo raciocínio vale para todos os outros campos dessa resposta. Você está usando os valores de hoje para todos eles.
| Campo | Como muda | O que quebra |
|---|---|---|
| status | TRADING → SETTLING → removido | Viés de sobrevivência; saídas fictícias a um preço de fechamento que nunca foi negociado |
| onboardDate | Novas listagens toda semana; ausente nos símbolos removidos | Negociação de contratos antes de existirem |
| tickSize / stepSize | Ajustados conforme os níveis de preço mudam | Arredondamento de ordens e preços limite que teriam sido rejeitados |
| minNotional | Elevado ao longo do tempo em livros com pouca liquidez | Posições pequenas que seu roteador de ordens ao vivo recusaria |
| fundingIntervalHours | 8h por anos, depois 4h ou 1h em muitos símbolos | Carry errado por 2–3× justamente nas altcoins que seu filtro mantém |
| leverage brackets | Faixas e margem de manutenção revisadas | Modelagem de liquidação e capacidade de margem |
No seu caso, o funding é o que mais pesa. Seu loop de acumulação presume três pagamentos por dia durante todo o histórico. Uma parte relevante da carteira de altcoins passou a usar funding a cada quatro horas, e é nas posições vendidas de ativos com funding alto que vem boa parte do seu PnL simulado. Não é uma diferença de arredondamento. O erro é de um fator inteiro.
A deslistagem em si é um evento, e você não está modelando esse evento
Na sua nova execução point-in-time, dei à estratégia uma saída generosa. Na prática, as deslistagens seguem um roteiro: um anúncio, normalmente com sete a quatorze dias de antecedência, depois um período em que só é permitido reduzir posições e, por fim, liquidação forçada a um preço de marcação. O anúncio é informação pública, e você pode agir com base nele. Portanto, a simulação mais fiel é sair no fechamento do dia do anúncio. Só que, numa deslistagem típica de altcoin, esse fechamento já está 10-20% abaixo do da semana anterior, o livro tem pouca liquidez e seu termo de slippage precisa levar em conta que o mercado está num regime diferente. Se seu motor de execução lhe dá o preço de liquidação sem impacto, você fez ativos em processo de extinção parecerem negociáveis pelo valor justo, sem perceber.
Se você nunca arquivou exchangeInfo, ainda dá para recuperar parte do histórico. O dump público em data.binance.vision/data/futures/um/monthly/klines/ ainda mantém diretórios de símbolos deslistados muito depois de a API esquecê-los. Liste os diretórios: o primeiro e o último arquivo mensal de cada símbolo dão uma janela viável de listagem e deslistagem, sem precisar de fornecedor. É uma reconstrução, não um registro, e não recupera tamanhos de tick nem intervalos de funding. Mas mostra quais símbolos existiam e quando, o que cobre 80% do que você precisa na segunda-feira.
O que eu pediria para você construir antes de mexer de novo no sinal
- Um cron que consulte exchangeInfo diariamente em cada plataforma que você pesquisa e grave os dados no armazenamento de objetos, usando a data como chave. Compactado com gzip, ocupa menos de meio megabyte. Dez anos custam um arredondamento na conta do S3 e dão a você uma capacidade de pesquisa que não será possível comprar depois.
- Um cadastro de ativos derivado desses snapshots: uma linha por (plataforma, símbolo, valid_from, valid_to), com todos os campos. Compare snapshots consecutivos para gerá-lo e trate qualquer alteração de campo como uma nova linha.
- Uma função de universo que exija um timestamp como argumento.
universe(ts), nuncauniverse(). Torne impossível usar outra opção por padrão, para que ninguém, nem você mesmo no futuro às 1am, recorra sem querer à lista de sobreviventes. - Uma validação pré-listagem no loader de dados: nenhum candle pode existir antes de
onboard_tsmenos um dia. Interrompa a execução; não emita apenas um aviso. - Um ID interno estável para cada instrumento, que sobreviva às renomeações, deixando o ticker como atributo secundário. Associe POL e MATIC ao mesmo ID e sinalize redenominações para que a lógica de continuidade possa impedir que sejam unidas.
Faça isso e rode o backtest de novo. Minha aposta é que você chegue perto do 0.74 que obtive, e aí a pergunta interessante será se um 0.74 que inclui os ativos mortos tem algo que valha a pena testar no paper trading. Talvez tenha. Momentum cross-sectional em perpétuos tem seu valor, e uma parte do que sobra depois de corrigir o universo é carry legítimo do lado vendido. Você também vai ver seus resultados de paper trading e do backtest começarem a concordar, porque o paper trading sempre usou um universo point-in-time. Ele nunca teve escolha.
P.S. Isso não é uma peculiaridade das criptos; aqui só fica mais evidente. Pesquisadores de ações enfrentam retornos de deslistagem e tickers reciclados desde que a CRSP começou a distribuí-los, e nos mercados de previsão isso vai ao extremo: por definição, todo contrato expira, então o universo é feito apenas de listagens e encerramentos. Se algum dia você portar esse filtro para a Kalshi, construa primeiro o cadastro de ativos. Lá não existe outro tipo de histórico.
← Todos os artigos


