Direto ao ponto. Com os 34 robôs já rodando em conta simulada, mandei um modelo novo revisar a metodologia inteira. Voltaram 39 achados e 14 bugs confirmados. O pior deles: a régua que mede o risco do portfólio subestimava o tombo, e os 34 que estavam no ar furavam o próprio teto de 700 dólares por cerca de 11%, enquanto o painel mostrava folga. Com a régua corrigida, a esteira re-rodou e a composição foi de 34 para 35, depois 41, e terminou em 50 robôs. Era conta simulada. Por isso virou capítulo em vez de prejuízo.
O capítulo anterior terminou com o portfólio de 34 robôs no ar, em simulado, e com uma promessa: contar o que acontecesse, ganhando ou perdendo. O que aconteceu primeiro veio de dentro do meu próprio código. Uma auditoria nova achou 39 problemas, confirmou 14 e mostrou que a conta responsável por decidir quantos robôs cabem no meu teto de risco vinha medindo menos risco do que existe.
Medido do jeito certo, o pior caso dos 34 robôs que já estavam operando saltava de 689 para 775,60 dólares, contra um teto de 700. Eles furavam por 11% o mesmo limite que os aprovou, e o painel que eu abria todo dia dizia que cabia com 11 dólares de folga. Essa é a frase inteira do capítulo. Eu tinha subido um portfólio que a minha própria régua reprovaria, se a régua estivesse certa.
Pedi uma revisão de rotina e levei 14 bugs
O pedido foi curto. Revise toda a metodologia que eu criei para montar o portfólio que está rodando agora, procure bugs, falhas de conceito e proponha melhorias. Eu esperava um relatório morno. Esse código já tinha passado por auditoria atrás de auditoria: uma pegou 14 bugs numa das estações, outra pegou 11 em seis rodadas, e a checagem que virou o capítulo oito cortou o portfólio de 60 para 34 robôs antes do deploy. Voltaram 39 achados.
Foram duas rodadas em dois dias, com cerca de 78 agentes varrendo oito dimensões separadas do projeto, e cada achado grave foi entregue a um segundo agente com uma ordem só, derrubar o achado com número na mão. Antes de acusar, o auditor teve que reproduzir ao centavo o pior caso do portfólio no ar, com uma reimplementação própria.
Eu poderia ter confiado nas auditorias anteriores e tocado para o dinheiro real. Auditei outra vez porque toda auditoria é uma rede com malha de um tamanho, e uma rede mais fina sempre pega o que a anterior deixou passar. Dos 39 achados, 14 se confirmaram. Os dois primeiros atacavam o mesmo lugar.
O hedge que nunca existiu na conta
O motor que mede o risco das posições abertas marcava, num preço só, todas as posições de um par abertas em qualquer instante daquele dia. Uma compra que abriu às 8h e fechou às 10h entrava na mesma soma que uma venda aberta só às 14h. Na conta as duas se cancelavam. A compra perde quando o preço cai e a venda ganha, então o tombo do dia aparecia menor do que foi de verdade. Aquelas posições nunca estiveram abertas juntas. Era um hedge que nunca existiu.
Consertei netando só o que coexiste no tempo e não o dia inteiro de uma vez, e essa é a terceira vez que a mesma família de bug aparece por aqui. No capítulo oito, o risco em aberto era medido num retrato único às 23h59 e cegava o intradiário. Sempre na direção que faz o risco parecer menor, sempre no número que decide quanto capital vai para a mesa.
Comissão e swap que sumiam da conta
O segundo bug era mais bobo e igualmente caro. O leitor dos resultados dos grids lia só a coluna de lucro bruto e ignorava as colunas de comissão e swap, que a corretora cobra todo dia. Some isso em 12 grids ao longo de 2024 a 2026 e dá 424,54 dólares de custo escondido. Parece pouco dinheiro. Sozinho, esse bug já levava o pior caso a 712 dólares, acima do teto.
A proteção que eu achava pronta não disparava
A camada de proteção estava decorativa em três pontos. Dói mais porque é a parte que eu contei no capítulo quatro como o coração do método. O disjuntor por robô comparava o teto de drawdown vindo do Monte Carlo, feito de anos de resultado fechado, com o prejuízo flutuante instantâneo de uma única posição de 0,01 lote. Para bater esse teto, o preço teria que andar entre 1.500 e 2.900 pontos de uma vez. Ou seja: para 22 dos robôs o gatilho nunca dispararia.
O segundo ponto é pior de admitir. O teto de 700 dólares, a restrição que decidiu quantos robôs entram e que estrutura o projeto inteiro, não era vigiado por nenhuma linha de código ao vivo. O terceiro veio de fuso: o servidor da corretora trabalha seis horas à frente do meu, o monitor usava a minha hora local como limite da busca, e isso cegava o painel para as últimas seis horas de negócios fechados. Eram 8 de 70 negócios invisíveis.
O quarto é o mais perverso. Para trocar um robô, o procedimento reinicia o terminal, e os robôs de grid não reconstruíam o estado da cesta ao reiniciar, então o stop global que protege a cesta inteira ficava desarmado pelo resto da vida daquela posição. A rotina que existe para cortar um robô ruim criava, ela mesma, o cenário de perda sem freio nos outros. Nenhum dos quatro dava mensagem de erro.
O que eu troquei e por que o portfólio foi de 34 para 50
A régua foi reescrita para enxergar o tempo, e junto dela o atalho de cache que existe para medir subconjuntos rápido, e que repetia exatamente a mesma soma cega, também foi refeito e verificado batendo ao centavo com a conta direta em cinco subconjuntos. O fuso saiu em três lugares, o disjuntor de portfólio virou código de verdade no painel, o disjuntor por robô passou a usar o drawdown correto somando realizado e flutuante, e os robôs de grid ganharam reconstrução de estado ao reiniciar. Comecei o dia com 38 testes verdes. Terminei com 230.
Faltava a melhor decisão técnica do capítulo. O gerador que escreve o arquivo de configuração de cada robô não encontrava, por um erro de nome, os parâmetros otimizados de dois grids, e em vez de reclamar escrevia um arquivo quase vazio, então esses dois subiram para a conta com os valores de fábrica, numa configuração que nunca foi testada em lugar nenhum. Consertei o gerador para falhar alto. Só que conserto tapa causa conhecida e nada mais, então escrevi um portão independente, que confere antes de cada deploy se o que vai subir é exatamente o que a esteira aprovou. Rodei ele contra o deploy dos 34. Reprovou os dois grids e nomeou os parâmetros que faltavam.
Com a régua certa, re-rodei a esteira e ela cuspiu um portfólio diferente. Primeiro 35 robôs, pior caso em 699 dólares. Depois 41, quando o corte ganhou uma fase de reenchimento, porque ele parava no primeiro conjunto que coubesse no teto e existiam conjuntos maiores cabendo no mesmo teto. Os dois grids com configuração de fábrica caíram sozinhos na re-seleção. Saíram por contribuição de risco. O produto deste projeto sempre foi o método que monta o portfólio. A lista de robôs é consequência dele, e quando a máquina fica mais correta a lista muda.
Auditar o conserto pega o que o conserto deixou
Antes de mandar a esteira rodar de novo, pedi ao mesmo auditor uma olhada final nos meus próprios consertos. Achou mais quatro coisas. Uma era bug de verdade: a gravação do resultado acumulado do ciclo, que precisa sobreviver a um reinício, ficou completa num robô de grid e faltou em dois pontos de outro, então um reinício recuperaria o resultado do ciclo anterior e fecharia a cesta seguinte na hora errada. E o robô esquecido era o que roda nos 13 grids do candidato.
O quarto achado é o meu favorito. Quem pegou o bug foi o próprio teste. Escrevendo à mão o valor esperado da nova função de reenchimento, o teste falhou: três robôs de 10 dólares davam um pior caso de 30,000000000000004 dólares por resíduo de ponto flutuante, e o gate comparava esse valor cru contra o teto, o que rejeitaria um portfólio exatamente no limite por quatro quatrilionésimos de dólar. Uma tolerância de micro-centavo resolveu. É a quarta vez que este projeto tropeça na mesma pedra, e virou regra: a rodada de conserto precisa da própria auditoria. Yin e coautores mediram isso em sistemas grandes, e entre 14,8% e 24,4% dos consertos estudados estavam eles mesmos incorretos. Código de conserto entra sem auditoria nenhuma justo quando todo mundo está aliviado.
O log delatou um corte que aumentava o risco
Com tudo consertado, rodei a esteira numa pool maior. Eram 206 estratégias, com 32 clones exatos dentro. Dessa vez pedi um log linha a linha de cada etapa, do funil de ingestão até o gate final. Ele entregou dois furos invisíveis.
O primeiro é sutil. A etapa de corte respeita uma regra de não empilhar robôs que despencam juntos, mas a etapa de reenchimento que eu tinha acabado de criar escolhia os robôs de volta olhando só diversificação e teto, sem olhar essa lente de cauda. O resultado, provado com uma sondagem: 13 dos 14 pares perigosos do portfólio final envolviam um robô que tinha voltado pelo reenchimento. A fase que enchia o portfólio desfazia o trabalho da fase que o protegia.
O segundo furo ninguém enxerga sem a trilha linha a linha. Em cinco ou mais passos do corte, remover um robô aumentava o pior caso do portfólio, e num deles sair de 42 para 41 robôs levou o pior caso de 796 para 846 dólares. A causa é que o critério pesava tombo contra diversificação e podia eleger para a saída um robô que servia de contrapeso aos outros. Aí o corte passava do ponto, descia de 93 para 36 robôs, o reenchimento resgatava 21 de volta, e a composição final virava resultado do caminho.
Corrigido, o efeito na mesma pool foi limpo: os passos em que o risco subia caíram de cinco para zero, o corte parou em 45 robôs em vez de 36, o reenchimento virou ajuste fino de 5 em vez de resgate de 21, os pares perigosos caíram de 14 para 7 e a margem de corretora exigida caiu de 3.026 para 2.665 dólares. O candidato final ficou em 50 robôs. Sete a menos, a mesma diversificação e metade dos pares perigosos.
O que eu ainda não sei
A auditoria também pôs número numa suspeita minha. O número não me agradou. A esteira escolhe o portfólio minimizando o tombo numa janela e depois mede o tombo na mesma janela em que escolheu, tudo dentro da amostra. Medindo direito, o portfólio escolhido tem o menor tombo de todos: percentil zero contra sorteios do mesmo tamanho, 466 dólares contra uma mediana de 1.064. E ele infla 1,47 vez fora da janela de escolha, saindo de 466 dólares na seleção para 683 na janela seguinte. A folga de 1,5 que eu aplicava como margem contra crise é consumida quase inteira só para pagar o otimismo da própria escolha.
Sobram duas decisões abertas, e as duas são minhas. A premissa que eu mesmo escrevi manda dimensionar contra o dia em que tudo cai junto, o que exigiria uma folga bem maior que 1,5, e o candidato ainda trava 2.665 dólares de margem de corretora no pico, uma régua diferente do teto de tombo.
Um teste dessa leva ainda matou uma ideia minha, e é justo contar. Eu quis proibir no corte os pares que despencam juntos e pedi a prova de que isso ajuda. Três testes disseram que não. A estrutura de cauda medida dentro da amostra não persiste na janela seguinte, com correlação de posto de 0,06 entre uma era e a outra, e a fatia de pares perigosos que continuam perigosos, 6%, fica perto do que o acaso sozinho produz, 3%. Pior: contra 300 sorteios do mesmo tamanho medidos fora da amostra, o portfólio com o teto imposto teve cauda pior que 92% dos sorteios. É a mesma lição do capítulo dois, que eu levei duas vezes. Selecionar para minimizar um risco medido dentro da amostra reintroduz esse risco fora dela.
Auditar o que está no ar é diferente de auditar o papel
O capítulo nove tinha uma lição: construir é metade do trabalho e subir é a outra metade. A diferença entre auditar o papel e auditar o que já está rodando mora no estrago acumulado. No papel, um bug custa uma linha trocada. No ar, o mesmo bug já contaminou a medição da conta, já colocou dois robôs operando numa configuração que nunca foi testada, já deixou a proteção desligada por semanas e, o pior de tudo, já te deu confiança para tomar a próxima decisão de capital em cima de um número errado.
O único motivo de isso ter virado texto em vez de prejuízo foi a conta simulada. Os 34 furando o teto, os dois robôs com configuração de fábrica, o disjuntor de portfólio que não existia, o stop global desarmando a cada manutenção: nada disso custou um centavo. A conta demo é o para-choque que existe exatamente para o dia em que o bug aparece, e o bug sempre aparece.
Fica de pé a pergunta que sustenta o projeto desde o primeiro capítulo, e a auditoria a deixou mais afiada. Esse controle de risco todo sobrevive fora da amostra, ou é encaixe elegante em cima do passado? No próximo capítulo eu conto a prova que faltava: rodar a esteira inteira em janela deslizante, escolhendo os robôs numa era e medindo o resultado na era seguinte, cinco vezes seguidas, contra centenas de sorteios aleatórios do mesmo tamanho. Foi o teste que eu mais tive medo de rodar.
Acompanhe a série para não perder o resultado. O diário continua.
Perguntas frequentes
O que é um bug que subestima o risco?
É um erro de cálculo que faz o risco medido aparecer menor do que o real. E não gera mensagem de erro nenhuma. No meu caso, o motor somava posições que nunca estiveram abertas juntas e ignorava comissão e swap dos grids, escondendo 86 dólares de tombo.
Por que o número de robôs mudou de 34 para 50?
Porque a régua que decide quantos robôs cabem no teto foi corrigida, e um portfólio medido certo é um portfólio diferente. Com o cálculo consertado, a esteira montou 35, depois 41 ao ganhar uma fase que reenche o teto, e chegou a 50 numa pool maior. O número saiu da régua.
Referências
- Yin, Z.; Yuan, D.; Zhou, Y.; Pasupathy, S.; Bairavasundaram, L. (2011). How do fixes become bugs? 19th ACM SIGSOFT Symposium on Foundations of Software Engineering. Mede a fração de consertos que introduz defeito novo.
- 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. Por que uma métrica escolhida dentro da amostra piora fora dela.
- Leveson, N. G. (2011). Engineering a Safer World: Systems Thinking Applied to Safety. MIT Press. Sobre proteções previstas no projeto que falham na operação real.
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.
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
