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.

  1. API time ≠ chain time. Settlement lag, RPC lag, WebSocket lag — introducono tutti gap che il bot deve gestire esplicitamente.
  2. I retry richiedono idempotenza. Un retry senza client-order-id è un rischio di ordine duplicato. Sempre.
  3. Leggi esplicitamente ogni proprietà del market. Flag NegRisk, tick size, expiration. Non dare mai valori di default; leggi sempre dalla source of truth.
  4. 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.

Domande frequenti

Qual è l’errore più costoso di un Polymarket bot?
Andare live prima che il paper trading superi il gate dei 30 trade. L’abbiamo fatto. L’errore non è solo perdere soldi - è perdere la possibilità di imparare dalla strategia in un ambiente controllato. I bot che vanno live troppo presto vengono o nuked e abbandonati, oppure sprecano mesi a recuperare prima di tornare al paper trading.
Cos’è un phantom fill bug?
Quando il bot crede che un ordine sia stato eseguito ma l’exchange lo ha registrato come non ancora eseguito. Sintomi: la posizione appare nello stato dei bot ma non on-chain, con conseguenti ordini duplicati al retry. Risolto nel nostro trader tramite tre commit (e68a087, 8bb7761, 06deaef): usare FOK per i buy, interrogare lo stato finché non è matched, non fidarsi mai di status=delayed come se fosse filled.
Cos’è l’incidente di whipsaw lol-ctg-ccg?
Un mercato esports su un order book sottile in cui il nostro trader ha attivato uno stop-loss a -$2.55 a 0.14, per poi vedere il prezzo recuperare fino a 0.325 entro 2 minuti. Avevamo configurato lo stop-loss a -4 percentage points, troppo stretto per order book esports sottili. Fix: SL allargato a -8pp per i mercati a bassa liquidity, mantenendo uno SL più stretto solo per i book più profondi (NBA, calcio ad alta liquidity). Vedi memory/trader-sl-wider.md.
Come si è manifestato il bug del flag NegRisk?
Il bot piazzava ordini senza impostare neg_risk=true sui mercati multi-outcome. Gli ordini venivano rifiutati con messaggi di errore confusi, causando ritardi di diversi secondi prima del retry, e quindi fill mancati. Fix nel commit 06deaef: impostare sempre neg_risk in base ai metadata del market, senza mai assumere.
Cos’è stato l’incidente sleep-through-bug?
Il wallet è rimasto bloccato con un ordine incastrato alle 4am. Il proprietario ha detto al bot di fermarsi; ha toccato il file data/halt_autobuy. Il bot ha rilevato il file prima del successivo tentativo di trade e ha rifiutato di piazzare ordini. Il proprietario si è svegliato con uno stato pulito invece che peggiore. Ha validato il pattern halt-sentinel; ora lo includiamo di default in ogni bot.
Qual è la singola lezione più generalizzabile di questi postmortem?
Non fidarti mai del happy path. Ogni bug che abbiamo messo in produzione è nato dal presupposto che una richiesta fosse riuscita, che un fill fosse reale, o che un prezzo non si sarebbe mosso. Scrivi codice difensivo: assume che gli ordini falliscano, che le reconciliation divergano, che un mercato stia per fare qualcosa di strano. La paranoia tax è piccola; il costo di saltarla è il postmortem che scriverai dopo.