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.

  1. API-tijd ≠ chain-tijd. Settlement lag, RPC lag, WebSocket lag — allemaal introduceren gaps die bot-code expliciet moet behandelen.
  2. Retries hebben idempotentie nodig. Een retry zonder client-order-id is een dubbele-order risico. Altijd.
  3. Lees elke markt-eigenschap expliciet. NegRisk flag, tick size, expiratie. Default nooit; lees altijd uit de source of truth.
  4. 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.

Veelgestelde vragen

Wat is de duurste Polymarket bot fout?
Live gaan voordat paper-trading de 30-trade gate haalt. We hebben het gedaan. De fout is niet alleen geld verliezen — het is de kans verliezen om in een gecontroleerde omgeving van de strategie te leren. Bots die te vroeg live gaan worden ofwel genuked en achtergelaten, of verspillen maanden aan herstel voordat ze opnieuw paper-traden.
Wat is een phantom fill bug?
Wanneer de bot gelooft dat een order vulde maar de exchange registreert hem als nog niet gevuld. Symptomen: positie verschijnt in de state van je bot maar niet on-chain, leidend tot dubbele orders op retry. Gefixt in onze trader via drie commits (e68a087, 8bb7761, 06deaef): gebruik FOK voor buys, poll status tot matched, vertrouw status=delayed nooit als filled.
Wat is het lol-ctg-ccg whipsaw incident?
Een esports markt op een dun order book waar onze trader een -2,55 $ stop-loss vuurde op 0,14, en vervolgens de prijs zag herstellen naar 0,325 binnen 2 minuten. We hadden stop-loss geconfigureerd op -4 procentpunten wat te strak is voor dunne esports books. Fix: SL verbreed naar -8pp voor low-liquidity markten, strakkere SL alleen behouden voor dikke books (NBA, high-liquidity voetbal). Zie memory/trader-sl-wider.md.
Hoe manifesteerde de NegRisk flag bug zich?
Bot plaatste orders zonder neg_risk=true te zetten op multi-outcome markten. Orders rejecteerden met verwarrende foutmeldingen, leidend tot multi-seconde delays voor retry, leidend tot missed fills. Fix in commit 06deaef: zet altijd neg_risk per market metadata, neem nooit aan.
Wat was het sleep-through-bug incident?
Wallet kwam vast met een stuck order om 4 uur 's ochtends. Owner instrueerde bot om te halten; touched data/halt_autobuy file. Bot detecteerde het bestand voor de volgende trade-poging en weigerde orders te plaatsen. Owner werd wakker met een clean state in plaats van een slechtere. Valideerde het halt-sentinel patroon; we shipten het nu by default in elke bot.
Wat is de enkele meest generaliseerbare les uit deze postmortems?
Vertrouw het happy path nooit. Elke bug die we hebben geshipt kwam van aannemen dat een request slaagde, een fill echt was of een prijs niet zou bewegen. Codeer defensief: neem aan dat orders falen, dat reconciliations divergeren, dat één markt op het punt staat iets vreemds te doen. De paranoia-belasting is klein; de kost van het overslaan ervan is de postmortem die je later schrijft.