Um ano-calendário de candles de 1 minuto deveria ter 525,600 linhas. Nossa extração de 2025 dos perpétuos de BTCUSDT veio com 525,557 — faltaram 43 minutos, uma taxa de completude de 99.992%. Um perpétuo de uma altcoin de capitalização média na mesma corretora, na mesma extração e pelo mesmo fluxo de código veio com 1,247 minutos a menos: 99.76%. E uma série de minutos de ações dos EUA para o mesmo ano tinha cerca de 427,000 linhas a menos que a de cripto, o que não é uma lacuna, mas simplesmente um mercado que fecha.
Três desses números são banais. O que vale mil palavras é o 43.
Quatro coisas diferentes que parecem um buraco no seu dataframe
Antes dos 43, vamos à taxonomia, porque a maioria dos códigos que lidam com lacunas já erra nesta etapa. Quando falta uma linha no seu parquet local, você não sabe qual dessas situações a produziu, e a resposta correta varia para cada uma.
| Tipo | O que realmente aconteceu | Como aparece | Resposta adequada |
|---|---|---|---|
| Sem negociações | O mercado estava aberto; ninguém cruzou o spread naquele minuto | Candlestick de volume zero (OHLC todos iguais, trades=0) em alguns endpoints, linha ausente em outros | Mantenha. É informação real: ninguém quis negociar. |
| Corretora fora do ar | Motor de matching offline, com ou sem manutenção programada | Linha ausente, às vezes várias em sequência | Marque como desatualizado. Não opere durante a lacuna. |
| Ativo suspenso | Violação de banda LULD, notícia pendente, aviso de deslistagem | Linha ausente, seguida por uma negociação no leilão de reabertura | Marque como desatualizado e trate a reabertura como uma descontinuidade. |
| Falha sua | Paginação limitada por taxa, uma nova tentativa que descartou uma página, um bug de limite de fuso horário no loop | Linha ausente, indistinguível das situações acima | Detecte e busque os dados novamente. Esta é a situação que você consegue corrigir. |
As corretoras divergem justamente na primeira linha dessa tabela, e é aí que mora o problema. O endpoint de candles da Coinbase omite completamente os intervalos vazios, então o histórico de um par ilíquido fica cheio de buracos literais. As klines da Binance geralmente fornecem um candle sintético com volume 0 e open=high=low=close fixados no último preço negociado. É o mesmo fato subjacente, apresentado de duas formas diferentes; e um carregador que reindexa os dados em uma grade completa de minutos transforma uma forma na outra sem avisar. Confira o comportamento da sua corretora antes de escrever o código para preencher lacunas, não depois.
Os 1,247 minutos ausentes na altcoin eram quase todos da categoria um: livro raso às 04:00 UTC de um domingo, sem ninguém negociando. É incômodo, fácil de entender e, em grande parte, inofensivo, porque a estratégia não teria operado nesse horário mesmo. Foi justamente por isso que parei de olhar para esse caso e voltei aos 43.
Os 43 minutos não estavam espalhados
Se as ausências fossem uniformes, os 43 minutos ao longo de um ano apareceriam como 43 pontos isolados, um a cada oito dias e meio, cada um um erro de arredondamento. Não foi o que encontramos. Eles vieram em seis sequências: uma de 19 minutos consecutivos, uma de 11, duas de 4 e alguns pares. Seis eventos, não 43 acidentes.
E esses eventos dependem justamente daquilo que importa para você. As corretoras caem quando estão sobrecarregadas, e ficam sobrecarregadas quando o preço está se movendo. Agrupei cada hora do ano por volatilidade realizada e verifiquei onde estavam os minutos ausentes: 61% deles ocorreram no decil superior. A probabilidade incondicional de um minuto qualquer estar ausente é 0.008%. Condicionada a estar dentro de uma hora de volatilidade do decil superior, é de cerca de 0.05% — seis vezes maior, além de concentrada em sequências.
Então, a métrica de completude no seu painel de qualidade de dados mede a coisa errada. 99.992% parece um conjunto de dados em que você pode parar de pensar. Na prática, ela descreve uma série completa nas horas em que sua estratégia não faz nada e cheia de buracos nas horas em que faz tudo. Um sistema de momentum que dispara com a expansão da volatilidade tem uma chance materialmente maior de encontrar uma lacuna do que o número geral sugere, e a encontra no meio de uma operação.
O que o preenchimento para frente faz três linhas depois
Foi esta falha que me levou a escrever o texto. Pegue a sequência de 19 minutos. Higiene padrão: reindexe para a grade completa de minutos, preencha OHLC para frente a partir do último fechamento e defina o volume como zero. Agora a série é contínua e seus indicadores rodam sem um NaN à vista.
Esses 19 candles têm high == low == close. O true range é zero em cada um deles. Um ATR(14) calculado nessa janela, partindo de uma leitura pré-interrupção de cerca de 240 USDT, cai para aproximadamente 34 quando a corretora volta — os cinco candles reais que sobreviveram na janela respondem por toda a média. Agora passe isso a um dimensionador de posições escalonado pela volatilidade, do tipo comum size = risk_budget / ATR. O tamanho da posição aumenta sete vezes.
O próximo candle real é o registro da reabertura, e não é um candle tranquilo. No nosso caso, abriu 1.8% distante do último fechamento antes da interrupção. O backtest alegremente assumiu uma posição 7x diante de uma lacuna de 1.8%, com uma execução que não poderia ter existido, a um preço que ninguém estava cotando. Essa única operação sintética valeu mais que um mês de P&L legítimo na curva de patrimônio, na direção errada — e nasceu inteiramente de uma linha de limpeza de dados escrita para deixar o dataframe arrumado.
Descartar as linhas em vez de preenchê-las também não resolve; é o mesmo bug com outra aparência. Ao descartá-las, suas janelas retrospectivas indexadas por inteiros mentem: uma "EMA de 20 candles" agora cobre 39 minutos no relógio ao longo da interrupção, o retorno entre candles no limite é o salto inteiro de 1.8% tratado como um movimento de um minuto, e qualquer estimativa de volatilidade por candle interpreta isso como um evento de 60 sigma. Nada emite um alerta. O índice continua monotônico.
A reamostragem é onde o problema fica invisível
A maior parte das pesquisas não usa candles de 1 minuto; usa dados agregados, e a agregação mascara o problema. Reamostre para 5 minutos e uma lacuna de 19 minutos vira quatro candles, dos quais o primeiro e o último são parciais. O Pandas calcula um OHLC perfeitamente plausível com base em dois minutos sobreviventes e o identifica da mesma forma que um candle construído com cinco. Nada na saída os diferencia.
A solução mais simples que conheço: mantenha uma coluna bars_in_window em toda reamostragem e nunca a descarte. Um inteiro por linha, e todas as perguntas subsequentes sobre a confiabilidade de um candle passam a ter resposta. Também mantemos seconds_since_last_real_print, que contém a mesma informação em um formato que a camada de execução consegue usar.
Nossa política, por assim dizer
O que nossos agentes aplicam agora, em ordem:
- Nunca reindexe sem avisar. O carregador gera um manifesto de lacunas — início, fim, duração e em qual das quatro categorias acredita que cada uma se encaixa. Se uma sequência de minutos ausentes tiver menos de 3 candles e o volume nos candles ao redor for baixo, trata-se de um minuto sem negociações. Qualquer sequência mais longa durante o horário ativo é tratada como uma interrupção até prova em contrário.
- Busque os dados novamente antes de interpretá-los. Metade das nossas lacunas iniciais era causada por bugs de paginação. Uma segunda extração de outro endpoint ou de outro fornecedor resolve a categoria quatro e reduz o problema antes de ser necessário fazer qualquer avaliação.
- Use um limite de desatualização, não preencha lacunas. A estratégia recebe uma entrada
data_agee uma regra rígida: não abra novas posições quando o último registro real tiver mais de N candles, e encerre as posições abertas na reabertura somente com uma ordem a mercado cujo preço inclua um desconto explícito pelo risco da lacuna. Execuções que não poderiam ter acontecido são piores do que operações que não aconteceram. - Indicadores recebem NaN, não ficção. Preços preenchidos para frente nunca chegam à camada de atributos. Se não for possível calcular o ATR, ele fica indefinido, e indefinido significa posição zerada. Uma falha explícita é melhor do que um 7x silencioso.
- Relate o desempenho condicionado às lacunas. Todo backtest que entregamos mostra o P&L com as operações próximas a interrupções excluídas, ao lado do resultado geral. Se essas operações sustentam o resultado, ele é um artefato dos dados.
Uma auditoria rápida para fazer hoje: agrupe seus minutos ausentes em sequências consecutivas e depois confira qual proporção das operações do backtest abre ou fecha em até 30 minutos do início ou fim de uma sequência. Abaixo de 1%, as lacunas provavelmente não estão influenciando nada. Com 5% ou mais, a curva de patrimônio conta em parte uma história sobre o tempo de inatividade da corretora.
O sinal em que passei a confiar é o formato das ausências, não o tamanho delas. Um conjunto de dados com milhares de buracos dispersos em horários sem atividade costuma estar bem. Um conjunto com algumas poucas concentrações apertadas mostra que algo falha sob carga — e é justamente quando algo falha sob carga que sua estratégia está operando.
← Todos os artigos
