Um ano civil de barras de 1 minuto devia ter 525,600 linhas. A nossa recolha de 2025 dos contratos perpétuos BTCUSDT chegou com 525,557 — menos 43 minutos, uma taxa de completude de 99.992%. Um contrato perpétuo de um alt de média capitalização na mesma plataforma, com a mesma recolha e o mesmo código, chegou com menos 1,247: 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 do que a de cripto, o que não é uma lacuna, é apenas um mercado que fecha.
Três destes números são banais. O que vale por mil palavras é o 43.
Quatro coisas diferentes que parecem todas um buraco no seu dataframe
Antes de voltarmos ao 43, vejamos a taxonomia, porque a maioria do código para lidar com lacunas já está errada a este nível. Quando falta uma linha no seu parquet local, não sabe qual destas situações a originou, e a resposta correta varia em cada caso.
| Tipo | O que aconteceu realmente | Como aparece | Resposta adequada |
|---|---|---|---|
| Sem transações | O mercado estava aberto; ninguém cruzou o spread nesse minuto | Barra de volume zero (OHLC todos iguais, trades=0) em alguns endpoints; linha ausente noutros | Mantenha-a. É informação real: ninguém quis negociar. |
| Plataforma indisponível | Motor de correspondência offline, com ou sem aviso prévio | Linha ausente, por vezes em sequência | Marque como desatualizada. Não negoceie durante esse período. |
| Interrupção do instrumento | Violação do limite LULD, notícias pendentes, aviso de exclusão de cotação | Linha ausente, seguida de uma transação no leilão de reabertura | Marque como desatualizada e trate a reabertura como uma descontinuidade. |
| Erro seu | Paginação limitada por taxa, uma nova tentativa que ignorou uma página, um erro de limite de fuso horário no ciclo | Linha ausente, indistinguível dos casos acima | Detete e volte a recolher. Este é o único caso que pode corrigir. |
As plataformas divergem quanto à primeira linha da tabela, e é aí que está o problema. O endpoint de candles da Coinbase omite por completo os intervalos vazios, pelo que o histórico de um par pouco líquido está cheio de buracos literais. As klines da Binance costumam fornecer uma barra sintética com volume 0 e open=high=low=close fixados na transação anterior. O mesmo facto subjacente, duas formas diferentes; e um carregador que reindexa para uma grelha de minutos completa transforma uma na outra sem o avisar. Verifique o comportamento da sua plataforma antes de escrever o preenchimento de lacunas, não depois.
Os 1,247 minutos em falta no alt pertenciam quase todos à categoria um: um livro de ofertas pouco espesso às 04:00 UTC de domingo, sem ninguém a negociar. Irritante, fácil de compreender e praticamente inofensivo, porque a estratégia também não teria negociado nessa altura. Foi precisamente por isso que deixei de olhar para esses dados e voltei aos 43.
Os 43 minutos não estavam dispersos
Se as ausências fossem uniformes, 43 minutos ao longo de um ano surgiriam como 43 ocorrências isoladas, uma a cada oito dias e meio, cada uma um erro de arredondamento. Não foi isso que obtivemos. Surgiram em seis sequências: uma de 19 minutos consecutivos, uma de 11, duas de 4 e mais alguns pares. Seis eventos, não 43 acidentes.
E esses eventos dependem daquilo que lhe interessa. As plataformas ficam indisponíveis quando estão sob carga, e estão sob carga quando o preço se move. Agrupei todas as horas do ano por volatilidade realizada e verifiquei onde se encontravam os minutos em falta: 61% caíam no decil superior. A probabilidade incondicional de faltar um determinado minuto é 0.008%. Condicionada a estar numa hora de volatilidade do decil superior, é cerca de 0.05% — seis vezes mais alta, além de surgir em grupos.
Assim, a métrica de completude no painel de qualidade dos seus dados está a medir a coisa errada. 99.992% parece descrever um conjunto de dados em que já não precisa de pensar. Na realidade, descreve uma série completa nas horas em que a 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 probabilidade consideravelmente maior de encontrar uma lacuna do que o valor principal sugere, e encontra-a a meio de uma operação.
O que o preenchimento para a frente faz três linhas depois
Foi a falha que me levou a escrever isto. Consideremos a sequência de 19 minutos. Procedimento habitual: reindexar para a grelha completa de minutos, preencher OHLC para a frente com o último fecho e definir o volume como zero. A série fica contínua e os indicadores executam sem um único NaN à vista.
Essas 19 barras têm high == low == close. A amplitude verdadeira é zero em todas elas. Um ATR(14) calculado nessa janela, partindo de uma leitura antes da interrupção de cerca de 240 USDT, desce para aproximadamente 34 quando a plataforma regressa — as cinco barras reais sobreviventes na janela sustentam toda a média. Agora introduza isso num dimensionador de posições ajustado à volatilidade, do tipo comum size = risk_budget / ATR. O tamanho aumenta sete vezes.
A barra real seguinte é o registo da reabertura, e não é uma barra tranquila. No nosso caso, abriu 1.8% afastada do último fecho antes da interrupção. O backtest abriu alegremente uma posição 7x perante uma lacuna de 1.8%, com uma execução que não podia ter existido, a um preço que ninguém estava a cotar. Essa única operação sintética valia mais do que um mês de P&L legítimo na curva de capital, no sentido errado — e nasceu inteiramente de uma linha de limpeza de dados escrita para arrumar o dataframe.
Eliminar as linhas em vez de as preencher também não resolve; é o mesmo erro com outra aparência. Elimine-as e os seus lookbacks indexados por inteiros mentem: uma «EMA de 20 barras» passa a abranger 39 minutos de relógio durante a interrupção, o retorno entre barras no limite é o salto total de 1.8% tratado como movimento de um minuto, e qualquer estimativa de volatilidade por barra lê-o como um evento de 60-sigma. Nada o avisa. O índice continua monotónico.
A reamostragem é onde o problema fica invisível
A maior parte da investigação não usa barras de 1 minuto; usa dados agregados, e a agregação disfarça o problema. Reamostre para 5 minutos e um buraco de 19 minutos transforma-se em quatro barras, das quais a primeira e a última são parciais. O Pandas calcula um OHLC com aspeto perfeitamente razoável a partir de dois minutos sobreviventes e atribui-lhe o mesmo rótulo de uma barra construída com cinco. Nada no resultado permite distingui-las.
A solução mais simples que conheço: mantenha uma coluna bars_in_window em todas as reamostragens e nunca a elimine. Um inteiro por linha, e todas as perguntas posteriores sobre a fiabilidade de uma barra passam a ter resposta. Também mantemos seconds_since_last_real_print, que contém a mesma informação num formato que a camada de execução pode usar.
A política, na medida em que existe
O que os nossos agentes aplicam agora, por ordem:
- Nunca reindexe sem o indicar. O carregador produz um manifesto de lacunas — início, fim, duração e qual das quatro categorias lhe parece corresponder. Se uma sequência de minutos em falta tiver menos de 3 barras e o volume nas barras adjacentes for reduzido, trata-se de um minuto sem transações. Qualquer sequência mais longa durante horas de atividade é tratada como interrupção até prova em contrário.
- Volte a recolher os dados antes de os interpretar. Metade das nossas primeiras lacunas resultava de erros de paginação. Uma segunda recolha a partir de outro endpoint ou fornecedor resolve a categoria quatro e reduz o problema antes de ser necessário qualquer juízo.
- Bloqueio por dados desatualizados, não preenchimento de lacunas. A estratégia recebe um valor
data_agee uma regra estrita: não abrir novas posições quando o último registo real tiver mais de N barras, e fechar posições abertas na reabertura apenas através de uma ordem de mercado, com o preço ajustado por um desconto explícito para o risco de lacuna. Execuções que não poderiam ter acontecido são piores do que operações que não aconteceram. - Os indicadores recebem NaN, não ficção. Os preços preenchidos para a frente nunca chegam à camada de atributos. Se não for possível calcular o ATR, o valor fica indefinido, e indefinido significa posição neutra. Uma falha explícita é melhor do que um 7x silencioso.
- Apresente o desempenho condicionado às lacunas. Todos os backtests que publicamos mostram o P&L com as operações junto a interrupções excluídas, além do valor principal. Se essas operações sustentam o resultado, então o resultado é um artefacto dos dados.
Uma auditoria rápida que pode fazer hoje: agrupe os minutos em falta em sequências consecutivas e verifique que fração das operações do seu backtest abre ou fecha nos 30 minutos anteriores ou posteriores ao limite de uma sequência. Se for inferior a 1%, provavelmente as lacunas não estão a influenciar nada. Se for 5% ou mais, a curva de capital conta em parte uma história de indisponibilidade das plataformas.
O sinal em que aprendi a confiar é a forma como as ausências se distribuem, não a sua quantidade. Um conjunto de dados com milhares de buracos dispersos em horas mortas costuma estar bem. Um conjunto de dados com alguns grupos compactos está a dizer-lhe que algo falha sob carga, e é precisamente nessas condições que a sua estratégia atua.
← Todos os artigos
