Seis semanas depois de executarmos um backtest, executamos novamente com o mesmo código, a mesma configuração e o mesmo intervalo de datas. O patrimônio final estava diferente. Não muito diferente, mas o bastante para fazer o Sharpe passar de 1.31 para 1.08 e mudar qual conjunto de parâmetros parecia melhor.
A estratégia não havia mudado. Os dados históricos, sim. Um fornecedor revisou vários dias de negociações e candles, e nossa primeira execução não havia preservado o snapshot exato usado como entrada. Passamos os dias seguintes tentando descobrir se o novo resultado vinha de uma correção nos dados, de um bug no pipeline ou de um processo de pesquisa que não conseguíamos reproduzir.
Segunda-feira: a nova execução divergiu
A primeira pista foi uma pequena diferença em um par spot com alta liquidez. Nosso candle original mostrava uma máxima de 62,418. A nova execução mostrava 62,431. A diferença parecia inofensiva até rastrearmos a entrada da estratégia: o limite ficava entre esses preços. Alguns candles diferentes significavam sinais diferentes e, alguns dias depois, posições diferentes.
Comparamos a contagem diária de negociações e descobrimos que a divergência começava em uma única data. Em seguida, comparamos as linhas brutas que alimentavam essa data. A resposta atual do fornecedor incluía algumas negociações corrigidas e um candle agregado revisado. A resposta que usamos não vinha acompanhada de um registro de alterações.
Esse comportamento dos dados é normal. As exchanges corrigem registros; os fornecedores preenchem lacunas, corrigem negociações com preços incorretos e ajustam a lógica de agregação. Uma consulta por “dados de janeiro” pode retornar uma resposta diferente em março.
Terça-feira: encontramos o comprovante que faltava
Tínhamos registrado o nome do fornecedor, o símbolo e o intervalo de datas no registro da execução. Não tínhamos armazenado a própria resposta nem um hash do conteúdo de cada partição de entrada. Isso significava que podíamos provar o que havíamos solicitado, mas não quais bytes o fornecedor havia retornado.
Buscamos os arquivos em cache mais antigos que conseguimos encontrar e os comparamos com a resposta atual. A diferença não vinha de um ajuste por desdobramento nem de uma conversão de fuso horário. Era um pequeno conjunto de registros de negociações corrigidos que passava pelo nosso gerador de candles. Isso descartou uma hipótese, mas o cache estava incompleto, então ainda não conseguíamos reconstruir com exatidão todos os candles afetados.
| O que registramos | O que conseguimos responder | O que faltava |
|---|---|---|
| Fornecedor, símbolo, intervalo de datas | Qual solicitação a execução fez | O conteúdo da resposta |
| Versão do pipeline | Qual código gerou os candles | As linhas exatas de entrada |
| Horário da execução | Quando executamos o job | Qual revisão do fornecedor estava disponível naquele momento |
Tratávamos “mesma consulta” como “mesmos dados”. Essa suposição fazia o experimento parecer mais reproduzível do que realmente era.
Quarta-feira: a correção mudou a história
Reprocessamos as datas afetadas usando as duas versões dos dados disponíveis. Os candles revisados eliminaram duas entradas e anteciparam uma saída em um candle. Essas três operações explicavam a maior parte da mudança no Sharpe. A estratégia era sensível a algumas observações próximas do gatilho, embora a série geral de preços parecesse quase idêntica no gráfico.
Isso não significava que o resultado original era fraudulento nem que o resultado revisado era automaticamente melhor. Significava que a afirmação precisava de um escopo: a execução antiga descrevia o snapshot dos dados que tínhamos na época, enquanto a nova execução descrevia o histórico atual do fornecedor. Não podíamos chamar honestamente uma de reprodução da outra.
Quinta-feira: mudamos o registro da pesquisa
Passamos a adicionar snapshots imutáveis de entrada às novas execuções de pesquisa. Cada partição recebe um hash de conteúdo, e o manifesto da execução registra a fonte, o horário de obtenção, a versão do esquema, a política de ajustes e a versão do código de transformação. Para conjuntos de dados grandes, guardamos os arquivos em armazenamento endereçado por conteúdo, assim partições idênticas podem ser compartilhadas sem sobrescrever versões anteriores.
Quando as regras de armazenamento ou licenciamento nos impedem de reter os dados brutos, preservamos o comprovante mais completo permitido pelos termos: hashes, parâmetros da solicitação, identificadores de revisão da fonte e uma pequena amostra de linhas já interpretadas para diagnóstico. Um hash não recupera os dados, mas pode nos dizer quando duas execuções não consumiram bytes idênticos.
Um backtest reproduzível precisa de uma identidade para os dados de entrada, não apenas de uma URL de dados. Registre o que chegou, quando chegou e como isso se transformou nas linhas consumidas pela sua estratégia.
Sexta-feira: o que pularíamos da próxima vez
Pularíamos a comparação apenas das métricas principais. Uma mudança no Sharpe indica que algo mudou; não revela a causa. Comece pelas contagens de linhas e pelos hashes das partições, depois encontre a primeira data de entrada divergente. A partir daí, compare, nessa ordem, as linhas transformadas, os sinais, as ordens e as execuções.
Também deixaríamos de substituir silenciosamente os dados antigos pelo histórico corrigido. As correções são úteis, mas as execuções antigas devem continuar vinculadas às entradas originais. Um novo conjunto de dados deve gerar uma nova execução, com uma relação visível com a anterior.
Nossa regra prática agora é simples: se a intenção de uma nova execução é reproduzir um experimento, fixe o snapshot dos dados. Se a intenção é aproveitar um histórico corrigido, identifique-a como um novo experimento. Levamos alguns dias para aprender essa distinção, e ela cabe em uma linha do manifesto da execução.
← Todos os artigos


