Polymarket Bot Tutorial · Capítulo 32 de 32
Erros reais de Polymarket bot e postmortems: phantom fills, sticky-fail dedup, whipsaw de lol-ctg-ccg, bug de NegRisk flag, go-live prematuro - com os commits e datas que corrigiram cada um.
O que este capítulo cobre
Nosso próprio diário de produção de bugs que custaram dinheiro de verdade. O padrão importa mais do que os detalhes - as mesmas classes de bug se repetem entre bots, e a cura geralmente é um watchdog ausente, não uma estratégia melhor. Este capítulo foi feito para poupar sua mensalidade.
- Phantom fills (commits e68a087, 8bb7761)
- Bug de NegRisk flag (commit 06deaef)
- Sticky-fail dedup (commit 4c0bef1)
- Incidente de whipsaw: lol-ctg-ccg
- Go-live prematuro: wipe de 2025
- Sleep-through-bug: kill switch funcionou
- Lições que se generalizam
Phantom fills (commits e68a087, 8bb7761)
O primeiro grande incidente de phantom-fill no nosso trader, em maio de 2025. O bot colocou 22 compras FOK, todas executadas no CLOB. O bot imediatamente tentou postar 22 vendas GTC. 8 delas foram rejeitadas com "balance: 0 / sum of active orders: 0 / order amount: 10000000."
Causa raiz: atraso de settlement (capítulo 12). O CLOB casou em 100ms, o bot publicou a venda em 200ms, mas a transferência ERC-1155 na Polygon levou cerca de 2 segundos. O CLOB rejeitou a venda porque a chain ainda mostrava saldo zero.
Correção: inserir uma espera bloqueante de 5 segundos entre qualquer compra bem-sucedida e qualquer follow-up GTC no mesmo token. Commits e68a087 e 8bb7761. Zero incidentes de phantom-fill desde então.
Lição: tempo da API e tempo da chain são linhas do tempo diferentes. Código que assume que elas são síncronas vai cair exatamente nesse modo de falha.
Bug de NegRisk flag (commit 06deaef)
Um evento NegRisk multi-outcome com 8 candidatos teve um arb momentâneo de 1,8c (soma dos YES asks = 0,982). Nosso arber disparou todas as 8 compras FOK. 6 delas executaram; 2 foram liquidadas no contrato de exchange errado.
Causa raiz: o bot chamava createAndPostOrder sem definir negRisk: true no objeto de flags. Dois dos mercados tinham uma data histórica de criação diferente e exigiam a flag; seis não precisavam dela porque o contrato subjacente já estava roteando via NegRisk por padrão.
Correção: ler market.negRisk do Gamma para todo mercado e repassar para toda chamada de ordem. Commit 06deaef. Reexecutamos o arb com a flag definida; os 2 restantes foram liquidados corretamente.
Lição: nunca defina um property de mercado por default. Leia-o explicitamente da source of truth toda vez.
Sticky-fail dedup (commit 4c0bef1)
O bot tentou novamente uma compra que falhou 5 vezes em 12 segundos. A primeira tentativa na verdade foi bem-sucedida (um timeout de rede fez o bot não ver a resposta); as 4 tentativas seguintes criaram 4 posições adicionais. Total: 5 posições no mesmo mercado quando queríamos 1.
Causa raiz: não havia client-order-id idempotente. A lógica de retry do bot era "se falhou, tente de novo com um novo salt". O CLOB não tinha como reconhecer os retries como duplicatas.
Correção: gerar um UUID determinístico por ordem pretendida antes da primeira tentativa. Todos os retries usam o mesmo client-order-id, permitindo que o CLOB faça dedup. Commit 4c0bef1.
Lição: retries sem idempotence são duplicatas. Toda ordem precisa de um identificador estável no lado do cliente.
Incidente de whipsaw: lol-ctg-ccg
Uma partida de esports (CTG vs CCG) fez o bot entrar comprado em 0,45 quando o imbalance virou positivo. Em 30 segundos, o imbalance virou negativo e nossa venda GTC em 0,50 foi executada pela ordem de outra pessoa. PnL: +5c × 10 shares = +$0.50.
10 minutos depois, o imbalance do mesmo mercado virou positivo de novo. O bot entrou novamente em 0,42. Desta vez o imbalance nunca se recuperou; o mid derivou até 0,18 e a posição foi levada até a resolução em 0.
Causa raiz: a estratégia tratava imbalance como sinal direcional, mas não acompanhava que o imbalance estava oscilando - ambos os sinais eram ruído, não informação. O bot foi whipsawed entre dois sinais falhos no mesmo mercado em menos de 20 minutos.
Correção: cooldown por mercado - após um fill, nenhuma nova entrada no mesmo mercado por 30 minutos. Permitidas múltiplas entradas em mercados diferentes, mas não em sequência no mesmo.
Lição: um sinal que oscila não é um sinal. Filtre por persistência antes de agir.
Go-live prematuro: wipe de 2025
Uma nova estratégia de market-making passou 12 paper trades. O builder não esperou 30, decidiu que "estava bonito", fez deploy ao vivo com $500 de capital. Em 18 horas a wallet estava em $200.
Causa raiz: 12 trades não é amostra suficiente para distinguir 60% WR de 35% WR. Na verdade, a estratégia era 35% WR; a janela de 12 paper trades só teve uma sequência não representativa.
O gate de 30 trades existe por um motivo. A variância de uma amostra de 12 trades torna impossível diferenciá-la de "a estratégia não funciona".
Lição: disciplina vence convicção. O gate de 30 trades não é negociável.
Sleep-through-bug: kill switch funcionou
O bot tinha um off-by-one no filtro de horário do dia - era para pausar às 02:00 UTC, mas na verdade estava pausando às 03:00 UTC. Durante a hora 02:00-03:00 sem pausa, o Polygon RPC estava limitando fortemente nossas requests; o caminho de leitura do bot estava retornando dados desatualizados.
O bot continuou operando com preços stale. PnL na hora: -$3.20 em 22 trades. O kill switch de perda diária disparou em -5%, interrompeu o bot e enviou um alerta no Telegram às 03:08 UTC. O builder acordou com o bot parado às 09:00; o dano total ficou limitado ao limite do kill switch.
Lição: o bug era real, mas o kill switch funcionou. -$3.20 em vez de -$50.00. Os controles de risco não impedem bugs; eles limitam o custo dos bugs que você não viu chegando.
Lições que se generalizam
Em todos os postmortems, quatro padrões se repetem.
- Tempo da API ≠ tempo da chain. Settlement lag, RPC lag, WebSocket lag - todos introduzem gaps que o código do bot precisa tratar explicitamente.
- Retries precisam de idempotence. Um retry sem client-order-id é risco de ordem duplicada. Sempre.
- Leia cada property de mercado explicitamente. NegRisk flag, tick size, expiration. Nunca use default; sempre leia da source of truth.
- O kill switch é o piso, não um recurso. Controles de risco limitam perdas em bugs. Estratégias não previnem bugs; elas assumem que o bot funciona corretamente. O bot nem sempre vai funcionar corretamente.
Todo capítulo desta série tem um desses padrões embutido em algum lugar. Eles são os princípios estruturais de um bot em produção. Pule-os e você os encontrará de novo nos seus próprios postmortems.





