Polymarket Bot Tutorial · Hoofdstuk 32 van 32
Echte Polymarket bot fouten en postmortems: phantom fills, sticky-fail dedup, lol-ctg-ccg whipsaw, NegRisk flag bug, premature go-live — met de commits en data die ze fixten.
Wat dit hoofdstuk behandelt
Ons eigen production-dagboek van bugs die echt geld kostten. Het patroon telt meer dan de details — dezelfde klassen bugs keren terug over bots heen, en de genezing is meestal een ontbrekende watchdog, geen betere strategie. Dit hoofdstuk is bedoeld om je het lesgeld te besparen.
Dit is hoofdstuk 32 van onze 32-delige serie over het bouwen van een Polymarket trading bot. We behandelen het onderwerp in detail in de secties hieronder. De body content voor elke sectie wordt geschreven en hoofdstuk-per-hoofdstuk uitgerold; FAQ-antwoorden en referenties zijn al compleet en weerspiegelen production-ervaring van het draaien van onze eigen trader.
- Phantom fills (commits e68a087, 8bb7761)
- NegRisk flag bug (commit 06deaef)
- Sticky-fail dedup (commit 4c0bef1)
- Whipsaw incident: lol-ctg-ccg
- Premature go-live: 2025 wipe
- Sleep-through-bug: kill switch werkte
- Lessen die generaliseren
Phantom fills (commits e68a087, 8bb7761)
Het eerste grote phantom-fill incident op onze trader, mei 2025. Bot plaatste 22 FOK buys, allemaal gematcht bij de CLOB. De bot probeerde direct 22 GTC sells te posten. 8 ervan rejecteerden met "balance: 0 / sum of active orders: 0 / order amount: 10000000."
Root cause: settlement lag (hoofdstuk 12). CLOB matchte in 100ms, de bot postte de sell in 200ms, maar de Polygon ERC-1155 transfer duurde ~2 seconden. De CLOB weigerde de sell omdat de chain nog steeds zero balance liet zien.
Fix: voeg een 5-seconden blocking wait toe tussen elke succesvolle buy en elke GTC follow-up op hetzelfde token. Commits e68a087 en 8bb7761. Nul phantom-fill incidenten sindsdien.
Les: API-tijd en chain-tijd zijn verschillende tijdslijnen. Code die aanneemt dat ze synchroon zijn raakt deze exacte failure mode.
NegRisk flag bug (commit 06deaef)
Een NegRisk multi-outcome event met 8 kandidaten had een momentane arb van 1,8c (som van YES asks = 0,982). Onze arber vuurde alle 8 FOK buys. 6 vulden; 2 settled in het verkeerde exchange contract.
Root cause: de bot riep createAndPostOrder aan zonder negRisk: true in het flags-object te zetten. Twee van de markten hadden een andere historische creation date en vereisten de flag; zes hadden hem niet nodig omdat hun onderliggende contract al by default routeerde via NegRisk.
Fix: lees market.negRisk uit Gamma voor elke markt, geef door aan elke order-call. Commit 06deaef. We draaiden de arb opnieuw met de flag gezet; de overgebleven 2 settled correct.
Les: default nooit een markt-eigenschap. Lees hem expliciet uit de source of truth elke keer.
Sticky-fail dedup (commit 4c0bef1)
De bot retryede een gefaalde buy 5 keer in 12 seconden. De eerste poging slaagde eigenlijk (netwerk-timeout zorgde dat de bot de respons niet zag); de volgende 4 retries creëerden 4 extra posities. Totaal: 5 posities op dezelfde markt waar we 1 wilden.
Root cause: geen idempotente client-order-id. De retry-logica van de bot was "als het faalde, probeer opnieuw met een nieuwe salt." De CLOB had geen manier om de retries als duplicaten te herkennen.
Fix: genereer een deterministische UUID per bedoelde order vóór de eerste poging. Alle retries gebruiken dezelfde client-order-id, waardoor de CLOB kan deduppen. Commit 4c0bef1.
Les: retries zonder idempotentie zijn duplicaten. Elke order heeft een stabiele client-side identifier nodig.
Whipsaw incident: lol-ctg-ccg
Een esports match (CTG vs CCG) had de bot een buy op 0,45 doen plaatsen toen imbalance positief flipte. Binnen 30 seconden flipte de imbalance negatief en onze GTC sell op 0,50 werd geraakt door iemands order. PnL: +5c × 10 shares = +0,50 $.
10 minuten later flipte de imbalance van dezelfde markt opnieuw positief. Bot trad opnieuw in op 0,42. Deze keer herstelde de imbalance nooit; mid dreef naar 0,18 en de positie reed tot resolution op 0.
Root cause: de strategie behandelde imbalance als een direction signaal maar trackte niet dat de imbalance stuiterde — beide signalen waren ruis, geen informatie. De bot werd whipsawed over twee gefaalde signalen op dezelfde markt binnen 20 minuten.
Fix: cooldown per markt — na een fill, geen nieuwe entries op dezelfde markt voor 30 minuten. Stond meerdere entries over verschillende markten toe, maar niet back-to-back op dezelfde.
Les: een signaal dat stuitert is geen signaal. Filter op persistentie voor het handelen.
Premature go-live: 2025 wipe
Een nieuwe market-making strategie passeerde 12 paper trades. De builder wachtte niet op 30, besloot "ziet er goed uit," deployde live met 500 $ kapitaal. Binnen 18 uur stond de wallet op 200 $.
Root cause: 12 trades is niet genoeg sample om 60% WR van 35% WR te onderscheiden. De strategie was eigenlijk 35% WR; het 12-trade paper-venster had toevallig een niet-representatieve streak.
De 30-trade gate bestaat met een reden. De variantie op een 12-trade sample maakt het niet te onderscheiden van "de strategie werkt niet."
Les: discipline verslaat overtuiging. De 30-trade gate is niet onderhandelbaar.
Sleep-through-bug: kill switch werkte
Bot had een off-by-one in zijn time-of-day filter — bedoeld om te pauzeren op 02:00 UTC, pauzeerde eigenlijk op 03:00 UTC. Tijdens het niet-gepauzeerde uur 02:00-03:00 was Polygon RPC onze requests zwaar aan het rate-limiten; het read-pad van de bot gaf stale data terug.
De bot bleef handelen op stale prijzen. PnL op het uur: -3,20 $ over 22 trades. De daily-loss kill switch triggerde op -5%, halte de bot, stuurde een Telegram alert om 03:08 UTC. Builder werd wakker met een gehalte bot om 09:00, totale schade beperkt tot de kill threshold.
Les: de bug was echt maar de kill switch werkte. -3,20 $ in plaats van -50,00 $. De risk controls voorkomen geen bugs; ze cappen de kost van bugs die je niet zag aankomen.
Lessen die generaliseren
Over alle postmortems heen herhalen vier patronen.
- API-tijd ≠ chain-tijd. Settlement lag, RPC lag, WebSocket lag — allemaal introduceren gaps die bot-code expliciet moet behandelen.
- Retries hebben idempotentie nodig. Een retry zonder client-order-id is een dubbele-order risico. Altijd.
- Lees elke markt-eigenschap expliciet. NegRisk flag, tick size, expiratie. Default nooit; lees altijd uit de source of truth.
- De kill switch is de bodem, geen feature. Risk controls cappen verliezen op bugs. Strategieën voorkomen geen bugs; ze nemen aan dat de bot correct werkt. De bot zal niet altijd correct werken.
Elk hoofdstuk in deze serie heeft ergens een van deze patronen ingebed. Het zijn de dragende principes van een productie-bot. Sla ze over en je vindt ze terug in je eigen postmortems.











