Existe uma pergunta da qual todo backtest foge: o mesmo código, alimentado pelo mesmo mercado ao vivo, teria feito a mesma coisa? Não dá para responder isso com mais backtest. Só dá para responder rodando a estratégia contra dados ao vivo, com semântica de execução real, e depois conferindo — trade a trade — se os dois mundos concordam. Fazemos essa conferência todo dia, de forma automática, e ela tem sido de longe o mecanismo mais produtivo de caça a bugs da plataforma.
Dia um: construir o espelho
Cada estratégia promovida roda em seu próprio nó isolado ao vivo, consumindo streams reais da exchange: cotações ao vivo para os fills, spreads reais, horários de sessão reais. Nenhum estado compartilhado entre estratégias (essa aprendemos do jeito difícil — um nó compartilhado deixava as consultas de posição de uma estratégia vazarem para outra). Cada fill é registrado junto com o valor do sinal que o originou.
Toda manhã: replay e diff
Um job diário pega a janela de paper de cada estratégia, roda o código idêntico pelo motor de backtest no mesmo período com dados do catálogo e casa os fills um a um: mesmo lado, mesma tolerância de timestamp, mesmo tamanho. O resultado não é uma correlação nem uma impressão. É uma tabela: casados, só no paper, só no replay. Uma estratégia saudável fica em 54 de 55 casados. Uma estratégia doente te diz exatamente qual trade divergiu e quando.
O que quebrou, na ordem em que quebrou
- Fills de warmup. O nó ao vivo alimentava histórico para aquecer os indicadores — e estratégias com handlers de barra customizados alegremente operavam em cima disso. Os backtests nunca viam essas entradas fantasmas. A correção foi uma trava na camada de submissão, não um ajuste de estratégia, porque senão toda estratégia herda o bug.
- Warmup sequencial multi-perna. Uma estratégia cross-sectional aquecia um instrumento por vez; cada perna via as irmãs vazias e a âncora nunca era inicializada. Ao vivo, ela não operava nada; no replay, operava normalmente. Zero trades casados — mas só o diff disse isso em voz alta.
- Crash loops de precisão de tick. Os ticks de aggTrade ao vivo chegam com uma precisão diferente da que o instrumento do sandbox esperava. O nó entrava em crash loop numa asserção que o backtest jamais conseguiria disparar, porque as barras do catálogo já vêm normalizadas.
- Amnésia de restart. Reinícios de processo redisparavam a lógica de entrada sobre posições que já existiam — para uma estratégia de rebalanceamento, um restart virava silenciosamente um rebalanceamento. No trading ao vivo isso não é uma nota de rodapé sobre paridade; é um alerta vermelho com dinheiro em jogo.
Repare no padrão: nenhum desses é um bug de sinal. São todos bugs de fronteira de execução — warmup, estado, precisão, restarts. É exatamente a classe de falha para a qual os backtests são estruturalmente cegos, porque num backtest o motor de backtest é a fronteira de execução.
As assimetrias que mantemos de propósito
Os dois mundos não são idênticos, e isso é deliberado. O backtest cobra um meio-spread modelado mais impacto por raiz quadrada dentro do PnL; o paper paga o spread real no preço do fill e não tem modelo de impacto. Somar o spread modelado ao paper (ou o real ao backtest) seria contar duas vezes. Paridade significa mesmas decisões, mesmos fills dentro da tolerância — não PnL idêntico bit a bit. Saber quais diferenças têm fundamento e quais são bugs é a maior parte da disciplina.
Um backtest é uma hipótese. Paper trading é o experimento. A paridade de replay é o caderno de laboratório que te pega enganando a si mesmo.
Se você roda estratégias e não compara o comportamento ao vivo com o comportamento reproduzido de forma agendada, existe uma categoria de bug que você nunca viu. Encontramos um a cada duas semanas, mais ou menos, e cada um deles era invisível de dentro do backtest.
← Todos os artigos
