Diário de um Portfólio de Robôs (5): O Bug que Inverteu Vencedores e Perdedores

Consertei um bug no robô de grid e os vencedores viraram perdedores. A história do overfitting ao bug, e por que um conserto de software funciona como um teste fora da amostra.

Direto ao ponto

Consertei um bug no robô de grid e o resultado virou de cabeça para baixo. O campeão da minha lista, que marcava +535 dólares, virou uma perda de 341. E um robô que eu daria como morto, com 1.088 no vermelho, virou +461. No total, o bug tinha inflado o resultado dos 23 robôs em cerca de 36%. A lição é dura: um robô pode ser vencedor não do mercado, mas de um bug.

No capítulo anterior eu montei a espinha do método: robôs descartáveis, disjuntor de drawdown, peso igual. Antes de confiar em qualquer número, fui auditar o robô de grid que entra no portfólio. Achei um bug. E o que veio depois de consertar foi o susto mais instrutivo do projeto inteiro, do tipo que muda a forma como você olha para qualquer backtest.

O bug: uma regra morta no meio da estratégia

A entrada desse grid depende de duas condições ao mesmo tempo. Uma é o sinal, uma sequência de candles na mesma direção. A outra é um tempo mínimo de espera depois de fechar a rodada anterior, antes de abrir uma rodada nova. O bug matou só a segunda. A memória do horário em que a rodada fechava era zerada por engano, então a trava de tempo nunca valia, e o robô reabria uma rodada nova na hora, em vez de esperar as horas definidas no seu parâmetro.

O detalhe importante é que o sinal de candles continuou funcionando 100%, no backtest com bug e no corrigido. Num dos robôs, o EUR-H3, foram 69 entradas, todas com a sequência de candles certa na direção certa, tanto na versão bugada quanto na corrigida. Só o relógio estava quebrado. Isso já é uma lição por si só: auditar um robô não é olhar se as entradas parecem certas. Uma entrada pode ter o gatilho certo, os candles na direção, e ainda assim estar errada por violar outra regra, a frequência. É preciso conferir cada condição do parâmetro contra a ordem real, isolada das outras. Foi assim que eu separei o sinal, que estava bom, da frequência, que estava bugada. Esse é o tipo de erro de backtest de grid que passa despercebido porque a curva continua bonita.

Teve até um susto de auditoria que virou aprendizado. Num dos robôs, a distância entre os aumentos parecia violar o mínimo do parâmetro: dava negativa, menos 0,02%, o que é fisicamente impossível. Eu quase tratei como um segundo bug. Não era. O robô confere a distância no preço de compra, o bid, mas preenche a ordem no preço de venda, o ask, e a diferença entre os dois, o spread, explicava a distância negativa. Era artefato de execução, não erro de lógica. A lição vale para qualquer um que audita robô: nem toda anomalia no dado é bug do seu código, parte é a mecânica real do mercado, e confundir as duas faz você consertar o que não está quebrado.

Com isso resolvido, validei o robô corrigido condição por condição, nos 23, antes de confiar em qualquer número. Conferi a trava de tempo da reentrada e do aumento, o hedge, o número máximo de aumentos, o alvo de lucro e o alinhamento com o timeframe. Zero violação real. O conserto fez exatamente o que tinha que fazer: a reabertura passou a respeitar a espera de cada robô, em vez dos zero hora de antes, e nada mais no comportamento mudou. Só aí o retest virou um teste em que dava para confiar.

O choque: o bug mentia nos dois sentidos

Retestei os 23 robôs aprovados com o robô corrigido, na mesma janela, com os mesmos parâmetros de cada um. Eu esperava uma piora geral. O que veio foi diferente e bem pior de entender: o bug mentia para os dois lados. Ele afundava robôs bons e inflava robôs ruins. Alguns exemplos:

Robô Com o bug Corrigido
EUR-H3 −35 +976
GBP-H3 −1.088 +461
EUR-M15 +237 +916
JPY-M2a (o “campeão”) +535 −341
EUR-M5 +521 +0
CAD-M6 +494 −266
Scatter dos 23 robos com bug contra corrigido, a nuvem se espalha e oito caem abaixo do zero

E não era só o resultado. O bug também escondia drawdown. O campeão JPY-M2a tinha um drawdown declarado de 242 dólares que, corrigido, virou 662. O GBP-M2 saltou de 438 para 1.017, e o CAD-M6 de 28 para 464. Ou seja, além de inflar o lucro de alguns, o bug fazia eles parecerem mais seguros do que eram. Somando tudo, os 23 robôs marcavam mais de 6.000 dólares com o bug e cerca de 3.900 corrigidos. O bug inflava em torno de 36% do resultado total.

Drawdown com bug contra corrigido: JPY-M2a 242 para 662, GBP-M2 438 para 1.017, CAD-M6 28 para 464

Por que vencedor virou perdedor

A pergunta que eu fiz na hora foi: como uma estratégia vencedora vira perdedora só de consertar um relógio? A resposta é a lição mais importante deste capítulo. Os robôs nunca foram vencedores de verdade. Eles eram vencedores da versão bugada. A otimização procurou, entre milhares de combinações, justamente as que davam lucro com o robô se comportando de forma bugada, reabrindo na hora, naquele exato pedaço de 2,5 anos. O otimizador não sabe o que o parâmetro significa. Ele guarda qualquer coisa que lucrou na amostra, inclusive o lucro que veio do bug.

Isso é overfitting puro, só que ao bug em vez de ao mercado. O ganho estava grudado em duas coisas ao mesmo tempo, o bug e aquele caminho específico de preços, e nenhuma das duas viaja para o futuro. Por isso consertar o bug funciona como um teste fora da amostra surpresa. Se o comportamento bugado fosse de fato bom, consertar quebraria todos os robôs. Mas consertou uns e quebrou outros, sem direção consistente. Essa falta de direção é a assinatura do ruído: cada robô tinha pescado uma coincidência diferente, não uma vantagem.

Como medir se uma ordenação sobrevive

Existe uma forma formal de olhar isso, e ela conecta com tudo que veio antes na série. Quando eu ordeno os robôs do melhor para o pior antes e depois do conserto, posso medir o quanto as duas ordens concordam com a correlação de postos de Spearman (1904):

\rho = 1 - \frac{6 \sum_{i} d_i^{2}}{n\,(n^{2}-1)}

Aqui n é o número de robôs e d_i é a diferença de posição de cada robô entre as duas ordenações, a com bug e a corrigida. Se a ordem fosse uma propriedade real dos robôs, uma mudança que não deveria importar manteria o rho perto de 1. Foi o contrário. O campeão caiu para o fundo e o pior subiu para o topo, então a ordenação se embaralhou e o rho desabou. E aqui está o ponto: quando uma classificação se desmancha diante de uma mudança que não deveria mexer nela, essa classificação nunca foi informação, era ruído. É exatamente a mesma lição do capítulo dois, em que selecionar os melhores robôs não sobrevivia a uma era nova. Lá a mudança foi o tempo. Aqui foi um conserto de software. O resultado é o mesmo: o que se embaralha facilmente nunca foi vantagem.

Soma dos 23 robos: 6.076 com o bug contra 3.899 corrigido, cerca de 36 por cento era o bug

O que eu mantive, e o que joguei fora

A decisão saiu direto da lógica acima. Mantive os robôs que ficaram melhores depois do conserto, porque eles ganham apesar da mudança, e são os mais prováveis de serem reais. Joguei fora os que viraram negativos, que eram artefato do bug. É contraintuitivo, porque entre os apagados estava o antigo campeão, e entre os mantidos estavam robôs que o relatório antigo dava como perdedores.

Tem uma ressalva honesta que eu faço questão de manter. A janela inclui 2024, o ano do caos no iene, então parte do vermelho dos robôs de iene é estresse de regime, não só o bug. O número limpo, o que mede de fato o efeito do bug, é a diferença na mesma janela, e essa diferença é enorme: só no JPY-M2a, o efeito do bug isolado do mercado foi de −876 dólares. O que esse episódio não permite é confundir as duas coisas, e por isso eu separo sempre o efeito do conserto do efeito do mercado.

E teve um segundo corte, porque sobreviver ao conserto não era suficiente. Entre os 15 que ficaram positivos, apliquei um filtro de qualidade: só mantive os que ganham bem em relação ao risco que correm, com lucro de pelo menos o dobro do drawdown. Usei lucro sobre drawdown, e não Sharpe, de propósito, porque o grid afunda em drawdown aberto antes de fechar a rodada, e o Sharpe não enxerga esse tipo de risco. Sobraram quatro. O CAD-M20, que lucra quase cinco vezes o seu drawdown, o EUR-M15 com 3,3, o EUR-H3 com 3,0 e o GBP-M5b com 2,2. Repare que nenhum dos quatro é de iene. A safra de USDJPY, que era a minha preferida e tinha o maior Sharpe in-sample de todas, 5,22, era também a mais contaminada pelo bug, e evaporou inteira no corte. Os outros onze positivos, de qualidade morna, também foram apagados. Não basta o robô ser real depois do conserto. Ele ainda precisa pagar pelo risco que carrega, senão entra no portfólio só enchendo espaço e drawdown.

Lucro sobre drawdown dos 4 grids que passaram no filtro de qualidade, corte em 2,0

A regra que sai disso, e o próximo capítulo

A regra que ficou para o resto do projeto é simples de dizer e trabalhosa de fazer. Antes de confiar em qualquer seleção, valide a aderência entre o parâmetro e a ordem: o que a ordem de fato fez bate com o que o arquivo de configuração mandava? A tentação seguinte é re-otimizar tudo no robô corrigido, mas isso vem depois, nunca antes: re-otimizar em cima de um robô cuja aderência você não validou é só repetir o erro numa escala maior. Eu quase fiz o oposto do certo aqui. Quase deletei os meus melhores robôs, os que o bug estava escondendo, e quase subi para o mercado os campeões falsos. Um detalhe de código separou as duas decisões. No próximo capítulo, outro risco que eu estava medindo errado, e que quase ninguém olha: o drawdown não dos trades fechados, mas das posições que ainda estão abertas.

Acompanhe a série para não perder. O próximo sai em poucos dias.

Perguntas frequentes

O que é “overfit ao bug”?

É quando a otimização guarda um comportamento bugado porque ele deu lucro na amostra. O ganho fica grudado na combinação de bug com aquele período específico, e nenhuma das duas coisas se repete no futuro. Consertar o bug revela isso, porque o lucro que dependia dele desaparece.

Como um bug-fix vira um teste fora da amostra?

Porque um robô com vantagem real deveria ser indiferente a uma mudança que não toca na sua tese. Quando o conserto reordena completamente os resultados, com campeão virando perdedor, isso prova que a ordenação era artefato. É o mesmo efeito de testar numa era nova, só que provocado por software.

Por que manter os robôs que melhoraram com o conserto?

Porque eles lucraram apesar da mudança, e não por causa dela. Robôs que dependiam do bug para parecer bons caem quando ele é corrigido. Robôs que sobem mesmo com a correção têm a maior chance de carregar algo real, não uma coincidência.

Um bug pode quebrar só parte do robô?

Sim. Neste caso ele matou a trava de tempo entre rodadas e deixou intacto o sinal de candles. Por isso é preciso checar cada condição do robô isoladamente contra as ordens reais, e não confiar que tudo está certo só porque as entradas parecem na direção certa.

Referências

  • Bailey, D. H.; Borwein, J. M.; López de Prado, M.; Zhu, Q. J. (2014). Pseudo-Mathematics and Financial Charlatanism: The Effects of Backtest Overfitting on Out-of-Sample Performance. SSRN 2308659 / Notices of the AMS.
  • Harvey, C. R.; Liu, Y.; Zhu, H. (2016). … and the Cross-Section of Expected Returns. Review of Financial Studies, 29(1), 5-68. O custo de testar muitas hipóteses sobre os mesmos dados.
  • Spearman, C. (1904). The Proof and Measurement of Association between Two Things. The American Journal of Psychology, 15(1), 72-101. A correlação de postos usada para medir se a ordenação sobreviveu.
  • Taranto, A. (2022). Bidirectional Grid Constrained Stochastic Processes. PhD Thesis, University of Southern Queensland, DOI 10.26192/q7q62. O grid como processo estruturalmente sobrevivível, porém não seguro.
  • López de Prado, M. (2018). Advances in Financial Machine Learning. Wiley. Sobre artefatos de backtest e a fragilidade de rankings ajustados à amostra.

Presente para Leitores: Robô de Gradiente Linear Gratuito

Estou liberando o acesso ao meu setup pessoal de Gradiente Linear sem custo nenhum. É só clicar e me pedir o arquivo.

Quero meu Robô Gratuito
🔒 Acesso Direto no WhatsApp
⚠️ Aviso de risco: O conteúdo do Invista Já é educacional e informativo sobre algotrading e estratégias quantitativas e não constitui recomendação ou consultoria de investimento, nem oferta ou solicitação de compra ou venda de qualquer ativo. Operações no mercado financeiro envolvem risco de perda, inclusive do capital investido, e resultados passados não garantem resultados futuros. Avalie seu perfil de risco e, se necessário, consulte um profissional certificado antes de investir.

Quem escreve: Flávio Araújo

Trader algorítmico há mais de 13 anos, MBA em Mercado de Capitais e Derivativos. Mantém um laboratório próprio que já rodou mais de 20 milhões de backtests e analisou mais de 20 mil estratégias, e opera um portfólio de robôs acompanhado publicamente no blog. LinkedIn · Instagram · YouTube

Flavio Araújo
Flavio Araújo

Engenheiro com MBA em Mercado de Capitais e Derivativos. Atua há mais de 10 anos no Mercado Financeiro, com 6 anos dedicados ao Algotrading e estratégias quantitativas. Especialista em validação de robustez e automação de investimentos.

Artigos: 164