Polymarket Bot Tutorial · Capitolo 32 di 32
Errori reali dei Polymarket bot e postmortem: phantom fills, sticky-fail dedup, whipsaw lol-ctg-ccg, bug del flag NegRisk, go-live prematuro - con i commit e le date che hanno risolto ciascun problema.
Cosa copre questo capitolo
Il nostro diario di produzione di bug che sono costati soldi veri. Il pattern conta più dei dettagli specifici — le stesse classi di bug ricorrono in tutti i bot, e la cura è di solito un watchdog mancante, non una strategia migliore. Questo capitolo serve a farti risparmiare la retta.
Questo è il capitolo 32 della nostra serie in 32 parti sulla creazione di un Polymarket trading bot. Trattiamo l’argomento in profondità nelle sezioni qui sotto. Il contenuto di ciascuna sezione viene scritto e pubblicato capitolo per capitolo; le risposte FAQ e i riferimenti sono già completi e riflettono l’esperienza di produzione maturata gestendo il nostro trader.
- Phantom fills (commit e68a087, 8bb7761)
- Bug del flag NegRisk (commit 06deaef)
- Sticky-fail dedup (commit 4c0bef1)
- Incidente di whipsaw: lol-ctg-ccg
- Go-live prematuro: wipe 2025
- Sleep-through-bug: il kill switch ha funzionato
- Lezioni generalizzabili
Phantom fills (commit e68a087, 8bb7761)
Il primo grande incidente di phantom fill sul nostro trader, maggio 2025. Il bot ha piazzato 22 buy FOK, tutti eseguiti sul CLOB. Il bot ha subito provato a pubblicare 22 sell GTC. 8 sono stati rifiutati con "balance: 0 / sum of active orders: 0 / order amount: 10000000."
Causa principale: settlement lag (capitolo 12). Il CLOB ha eseguito in 100ms, il bot ha pubblicato il sell in 200ms, ma il trasferimento ERC-1155 su Polygon ha richiesto circa 2 secondi. Il CLOB ha rifiutato il sell perché la chain mostrava ancora saldo zero.
Fix: inserire un’attesa bloccante di 5 secondi tra qualsiasi buy andato a buon fine e qualsiasi follow-up GTC sullo stesso token. Commit e68a087 e 8bb7761. Nessun altro incidente di phantom fill da allora.
Lezione: il tempo dell’API e il tempo della chain sono timeline diverse. Il codice che presume siano sincronizzati andrà incontro esattamente a questa modalità di errore.
Bug del flag NegRisk (commit 06deaef)
Un evento multi-outcome NegRisk con 8 candidati ha mostrato un arb momentaneo di 1,8c (somma delle ask YES = 0,982). Il nostro arber ha eseguito tutti e 8 i buy FOK. 6 sono stati eseguiti; 2 si sono regolati sul contratto di exchange sbagliato.
Causa principale: il bot chiamava createAndPostOrder senza impostare negRisk: true nell’oggetto flags. Due dei mercati avevano una data di creazione storica diversa e richiedevano il flag; sei non ne avevano bisogno perché il loro contratto sottostante passava già attraverso NegRisk per default.
Fix: leggere market.negRisk da Gamma per ogni mercato, e passarlo in tutte le chiamate ordine. Commit 06deaef. Abbiamo rieseguito l’arb con il flag impostato; i restanti 2 si sono regolati correttamente.
Lezione: non dare mai un valore di default a una proprietà del market. Leggila sempre esplicitamente dalla source of truth.
Sticky-fail dedup (commit 4c0bef1)
Il bot ha ritentato un buy fallito 5 volte in 12 secondi. Il primo tentativo in realtà è andato a buon fine (un timeout di rete ha fatto sì che il bot non vedesse la risposta); i 4 retry successivi hanno creato 4 posizioni aggiuntive. Totale: 5 posizioni sullo stesso mercato quando ne volevamo 1.
Causa principale: nessun client-order-id idempotente. La logica di retry del bot era "se fallisce, riprova con un nuovo salt." Il CLOB non aveva modo di riconoscere i retry come duplicati.
Fix: generare un UUID deterministico per ogni ordine previsto prima del primo tentativo. Tutti i retry usano lo stesso client-order-id, consentendo al CLOB di fare dedup. Commit 4c0bef1.
Lezione: i retry senza idempotenza sono duplicati. Ogni ordine ha bisogno di un identificatore stabile lato client.
Incidente di whipsaw: lol-ctg-ccg
Una partita esports (CTG vs CCG) ha visto il bot entrare in buy a 0.45 quando l’imbalance è passato in positivo. Entro 30 secondi, l’imbalance è passato in negativo e il nostro sell GTC a 0.50 è stato colpito dall’ordine di qualcun altro. PnL: +5c × 10 shares = +$0.50.
10 minuti dopo, l’imbalance dello stesso mercato è tornato positivo. Il bot è entrato di nuovo a 0.42. Questa volta l’imbalance non si è mai ripreso; il mid è scivolato fino a 0.18 e la posizione è arrivata a resolution a 0.
Causa principale: la strategia trattava l’imbalance come un segnale direzionale ma non tracciava il fatto che l’imbalance stesse rimbalzando — entrambi i segnali erano rumore, non informazione. Il bot è stato whipsawed su due segnali falliti nello stesso mercato nell’arco di 20 minuti.
Fix: cooldown per mercato — dopo un fill, nessun nuovo ingresso sullo stesso mercato per 30 minuti. Consentiti ingressi multipli su mercati diversi, ma non back-to-back sullo stesso.
Lezione: un segnale che rimbalza non è un segnale. Filtra la persistenza prima di agire.
Go-live prematuro: wipe 2025
Una nuova strategia di market making ha superato 12 paper trade. Il builder non ha aspettato i 30, ha deciso che "sembra buono", e l’ha messa live con $500 di capitale. Entro 18 ore il wallet era a $200.
Causa principale: 12 trade non sono un campione sufficiente per distinguere un WR del 60% da un WR del 35%. La strategia era in realtà al 35% di WR; la finestra di 12 trade nel paper trading conteneva per caso una streak non rappresentativa.
Il gate dei 30 trade esiste per un motivo. La varianza su un campione di 12 trade lo rende indistinguibile da "la strategia non funziona".
Lezione: la disciplina batte la convinzione. Il gate dei 30 trade non è negoziabile.
Sleep-through-bug: il kill switch ha funzionato
Il bot aveva un off-by-one nel filtro dell’ora del giorno — doveva mettere in pausa alle 02:00 UTC, ma in realtà si fermava alle 03:00 UTC. Durante l’ora 02:00-03:00 senza pausa, il Polygon RPC stava rate-limiting pesantemente le nostre richieste; il percorso di read del bot restituiva dati stale.
Il bot ha continuato a fare trading su prezzi stale. PnL nell’ora: -$3.20 su 22 trade. Il kill switch di daily-loss è scattato a -5%, ha bloccato il bot e ha inviato un alert Telegram alle 03:08 UTC. Il builder si è svegliato con un bot fermo alle 09:00, danno totale limitato alla soglia del kill switch.
Lezione: il bug era reale ma il kill switch ha funzionato. -$3.20 invece di -$50.00. I risk control non impediscono i bug; limitano il costo dei bug che non hai visto arrivare.
Lezioni generalizzabili
Tra tutti i postmortem, quattro pattern si ripetono.
- API time ≠ chain time. Settlement lag, RPC lag, WebSocket lag — introducono tutti gap che il bot deve gestire esplicitamente.
- I retry richiedono idempotenza. Un retry senza client-order-id è un rischio di ordine duplicato. Sempre.
- Leggi esplicitamente ogni proprietà del market. Flag NegRisk, tick size, expiration. Non dare mai valori di default; leggi sempre dalla source of truth.
- Il kill switch è il pavimento, non una feature. I risk control limitano le perdite sui bug. Le strategie non prevengono i bug; assumono che il bot funzioni correttamente. Il bot non funzionerà sempre correttamente.
Ogni capitolo di questa serie contiene da qualche parte uno di questi pattern. Sono i principi portanti di un bot in produzione. Saltali e li ritroverai nei tuoi postmortem.











