Um spike de viabilidade sobre momentum overnight no Solo_Trader devolveu um número bom demais: ganho mediano de +18% ao dia entre o fechamento e a abertura seguinte, com uma perna intraday igual e oposta. Em série diária de preço, uma mediana desse tamanho não é sinal forte. É sintoma. A primeira coisa que fizemos não foi montar a estratégia em cima disso, e sim desconfiar do dado — a pergunta na mesa era se aquilo era descoberta ou pipeline quebrado.
Era pipeline quebrado. O interessante não é o bug em si — é como ele passou por todas as validações que já rodavam sem acionar nenhuma delas.
O problema não estava dentro de nenhuma coluna
A validação que existia no pipeline trabalhava campo a campo. Cada coluna — open, high, low, close — passava confortavelmente: tipo certo, sem nulo, faixa de valor plausível, sem salto absurdo entre dias consecutivos. A série de close era internamente coerente, com retornos close-a-close que faziam sentido e rendiam um Sharpe estimado em ~0,74. Nenhuma daquelas checagens apontava qualquer coisa fora do lugar.
A causa raiz estava no encaixe entre as colunas. O close vinha ajustado por desdobramento e por dividendo. O open, o high e o low vinham ajustados só por desdobramento. Duas bases de preço diferentes convivendo na mesma linha da tabela, e o código tratando as quatro colunas como se fossem uma série só.
O salto overnight que o spike encontrou era, em boa parte, a diferença entre essas duas bases aparecendo como se fosse movimento de mercado. Nenhum limiar de magnitude teria pegado isso sem calibração, porque cada coluna, sozinha, era plausível.
LEIA TAMBÉM · OpiniãoO vendor tem o dado; o seu tier pode não expor no endpoint que você chamaUm fornecedor pode ter a informação que você precisa e, mesmo assim, o plano que você comprou não entregar essa informação no endereço que o seu programa consulta. Uma chamada de teste antes mostra isso.Trocamos o limiar de magnitude por um invariante entre colunas
A checagem que montamos cabe em uma frase: o fechamento tem que estar dentro do intervalo do dia. close ∈ [low, high], em toda linha, por definição do formato OHLC. O segundo invariante é ainda mais banal — high ≥ low. Nenhum dos dois olha o valor de uma coluna isolada. Os dois olham a relação entre colunas.
Isso muda a natureza da checagem. Um limiar de magnitude do tipo "retorno diário acima de X% é suspeito" precisa ser calibrado por ativo, por período e por regime de volatilidade, e erra nas duas pontas: deixa passar erro pequeno e acusa movimento real grande. O invariante físico não tem parâmetro para calibrar. Vale em qualquer nível de preço, em qualquer janela de tempo, para qualquer papel.
Na prática, roda dentro do pipeline do Solo_Trader — Python, Postgres, polars no processamento tabular, SQLAlchemy na camada de acesso, EODHD como vendor de OHLC. É uma comparação entre três colunas e uma contagem das linhas que violam. Não tem nada de sofisticado no cálculo, e essa é justamente a parte incômoda da história.
Entre 70% e 87% das linhas eram logicamente impossíveis
Rodamos o invariante contra o histórico esperando encontrar um punhado de linhas ruins. A checagem falhou em 70% a 87% das linhas, dependendo do recorte. Isso não é erro de borda: é a maior parte da série com um fechamento fora do intervalo do próprio dia, algo que não pode acontecer em dado correto — nem por arredondamento, nem por ativo ilíquido, nem por feriado mal tratado.
A surpresa não foi o percentual. Foi a validação anterior ter passado com folga sobre exatamente o mesmo arquivo. Autoconsistência de uma coluna não é integridade entre colunas. A série de close podia estar impecável — e estava — e ainda assim ser incompatível com as três colunas ao lado dela.
Todo pipeline que valida campo a campo tem esse ponto cego. Validação de schema não o cobre, porque o schema estava correto: quatro colunas numéricas, sem nulo, na ordem certa. O erro não era de forma. Era de significado.
O custo da checagem não é computacional
Implementar custou uma comparação a mais por reingestão. Qualquer banco relacional ou biblioteca tabular faz essa operação sem esforço: compara três colunas, conta as linhas que violam, devolve o número. Não é uma etapa que pese no tempo de processamento nem na conta do servidor.
Recomendamos a prática, e o preço dela aparece antes do código. É preciso conhecer a relação estrutural esperada entre os campos para escrever a checagem. Em OHLC ela vem de graça, dada pela definição do formato. Em um dataset de domínio menos padronizado, ninguém entrega o invariante pronto — alguém precisa sentar com quem conhece o domínio e apontar qual relação entre campos tem que valer sempre. Some a isso que a checagem roda depois da ingestão, sobre o dado já carregado: ela não substitui a validação de schema na entrada, que continua pegando outra classe de erro.
O mecanismo não é sobre preço, é sobre relação entre campos
Depois de reingerir a série numa base de ajuste consistente, recalculamos o sinal overnight. Sobrou um Sharpe entre ~0,74 e ~0,89: modesto, real, e a distância entre esse número e os +18% ao dia do começo é a medida exata do que a validação por coluna deixou passar.
O que mudou no processo é uma regra de ordem. Resultado que parece extremo dispara checagem de invariante antes de qualquer outra etapa de análise — antes de dimensionar posição, antes de escrever relatório, antes de mostrar o gráfico para alguém. E essa regra não é sobre OHLC. Vale para qualquer pipeline com relação estrutural conhecida entre campos: um total que tem que bater com a soma dos itens, uma data de fim que não pode vir antes da de início. Uma latitude não passa de 90 graus. Um percentual não passa de 100 nem fica negativo quando o domínio não permite.
A checagem por coluna continua necessária. Tipo certo, faixa plausível, nulo controlado — isso pega erro de ingestão e erro de formato, e nada disso sai do pipeline. Só que ela nunca vê a relação entre colunas, e é exatamente ali que mora o erro capaz de sobreviver a todas as outras validações e sair do outro lado parecendo um achado.
LEIA TAMBÉM · TutorialUm dashboard financeiro que só chama a IA depois que a estatística já filtrouUm sistema lê transações financeiras, dá uma pontuação de risco pra cada uma usando regras e estatística, e só chama uma IA para explicar as poucas que pareceram estranhas. O resultado aparece na tela assim que entra.O princípio que o caso deixou é curto: número extremo é gatilho de qualidade de dado até prova em contrário, não descoberta. Antes de comemorar um resultado fora da curva, a pergunta útil não é "quanto isso rende". É qual relação entre campos você poderia checar — e se ela se sustenta em toda linha.
