Polymarket Bot Tutorial · Розділ 32 із 32

Реальні помилки Polymarket bot і postmortems: phantom fills, sticky-fail dedup, lol-ctg-ccg whipsaw, NegRisk flag bug, premature go-live - з commits і датами, які виправили кожну з них.

Що охоплює цей розділ

Наш власний production diary багів, які коштували реальних грошей. Важливішим за деталі є сам патерн - ті самі класи багів повторюються в bot-ах, а ліки зазвичай не в кращій strategy, а у відсутньому watchdog. Цей розділ має заощадити вам навчання на власних помилках.

  • Phantom fills (commits e68a087, 8bb7761)
  • NegRisk flag bug (commit 06deaef)
  • Sticky-fail dedup (commit 4c0bef1)
  • Whipsaw incident: lol-ctg-ccg
  • Premature go-live: wipe 2025 року
  • Sleep-through-bug: kill switch спрацював
  • Уроки, що узагальнюються

Phantom fills (commits e68a087, 8bb7761)

Перший великий phantom-fill incident у нашому trader-і, травень 2025 року. Bot виставив 22 FOK buys, усі matched на CLOB. Bot одразу спробував виставити 22 GTC sells. 8 із них були rejected з повідомленням "balance: 0 / sum of active orders: 0 / order amount: 10000000."

Root cause: settlement lag (розділ 12). CLOB matched за 100ms, bot виставив sell за 200ms, але Polygon ERC-1155 transfer тривав приблизно 2 seconds. CLOB відхилив sell, бо chain усе ще показував нульовий balance.

Fix: вставити 5-second blocking wait між будь-яким успішним buy і будь-яким GTC follow-up на тому самому token. Commits e68a087 і 8bb7761. Відтоді - нуль phantom-fill incidents.

Lesson: API time і chain time - це різні timelines. Code, який припускає, що вони synchronous, зіткнеться саме з цим failure mode.

NegRisk flag bug (commit 06deaef)

NegRisk multi-outcome event із 8 candidates мав миттєвий arb на 1.8c (sum of YES asks = 0.982). Наш arber відправив усі 8 FOK buys. 6 із них filled; 2 settled у неправильний exchange contract.

Root cause: bot викликав createAndPostOrder, не встановивши negRisk: true в flags object. Два markets мали іншу historical creation date і потребували цього flag; шість - ні, бо їхній underlying contract уже маршрутизувався через NegRisk за замовчуванням.

Fix: читати market.negRisk із Gamma для кожного market, передавати це в кожен order call. Commit 06deaef. Ми повторно запустили arb із встановленим flag; решта 2 settled correctly.

Lesson: ніколи не задавайте market property за замовчуванням. Кожного разу читайте його явно з source of truth.

Sticky-fail dedup (commit 4c0bef1)

Bot повторив failed buy 5 разів за 12 seconds. Перша спроба насправді succeeded (network timeout спричинив те, що bot не побачив response); наступні 4 retries створили 4 додаткові positions. Разом: 5 positions на тому самому market, хоча ми хотіли 1.

Root cause: відсутній idempotent client-order-id. Retry logic у bot-а була такою: "if it failed, try again with a new salt." CLOB не мав способу розпізнати retries як duplicates.

Fix: генерувати deterministic UUID для кожного запланованого order перед першою спробою. Усі retries використовують той самий client-order-id, що дозволяє CLOB виконати dedup. Commit 4c0bef1.

Lesson: retries without idempotence are duplicates. Кожен order потребує стабільного client-side identifier.

Whipsaw incident: lol-ctg-ccg

Esports match (CTG vs CCG) спричинив buy на 0.45, коли imbalance став позитивним. Протягом 30 seconds imbalance став негативним, і наш GTC sell на 0.50 був виконаний чиєюсь іншою order. PnL: +5c × 10 shares = +$0.50.

10 minutes потому, imbalance у тому самому market знову став позитивним. Bot увійшов знову на 0.42. Цього разу imbalance так і не відновився; mid drifted до 0.18, і position дожила до resolution на 0.

Root cause: strategy сприймала imbalance як directional signal, але не відстежувала, що imbalance "скаче" - обидва signals були noise, а не information. Bot був whipsawed на двох хибних signals в одному й тому самому market протягом 20 minutes.

Fix: cooldown per market - після fill жодних нових entries у той самий market протягом 30 minutes. Дозволялося робити multiple entries у різних markets, але не back-to-back в одному й тому самому.

Lesson: signal, який "скаче", - це не signal. Перед дією перевіряйте persistence.

Premature go-live: wipe 2025 року

Нова market-making strategy пройшла 12 paper trades. Builder не дочекався 30, вирішив, що "виглядає добре", і запустив live з $500 capital. За 18 hours wallet упав до $200.

Root cause: 12 trades - це замала sample, щоб відрізнити 60% WR від 35% WR. Насправді strategy мала 35% WR; 12-trade paper window просто випадково мала нерепрезентативну streak.

30-trade gate існує не просто так. Variance на 12-trade sample робить його невідмінним від "strategy не працює".

Lesson: discipline beats conviction. 30-trade gate не підлягає обговоренню.

Sleep-through-bug: kill switch спрацював

Bot мав off-by-one у фільтрі часу доби - мав ставити паузу о 02:00 UTC, але насправді паузився о 03:00 UTC. Під час незупиненого вікна 02:00-03:00 Polygon RPC сильно rate-limited наші requests; read path bot-а повертав застарілі дані.

Bot продовжував торгувати на застарілих prices. PnL за годину: -$3.20 на 22 trades. Daily-loss kill switch спрацював на рівні -5%, зупинив bot і надіслав Telegram alert о 03:08 UTC. Builder прокинувся до зупиненого bot-а о 09:00; загальну шкоду було обмежено kill threshold.

Lesson: баг був реальним, але kill switch спрацював. -$3.20 замість -$50.00. Risk controls не запобігають багам; вони обмежують вартість багів, яких ви не передбачили.

Уроки, що узагальнюються

У всіх postmortems повторюються чотири патерни.

  1. API time ≠ chain time. Settlement lag, RPC lag, WebSocket lag - усе це створює розриви, які code bot-а має обробляти явно.
  2. Retries потребують idempotence. Retry без client-order-id - це ризик duplicate-order. Завжди.
  3. Читайте кожну market property явно. NegRisk flag, tick size, expiration. Ніколи не задавайте default; завжди читайте з source of truth.
  4. Kill switch - це підлога, а не feature. Risk controls обмежують збитки від багів. Strategies не запобігають багам; вони припускають, що bot працює correctly. Bot не завжди працюватиме correctly.

У кожному розділі цієї серії десь захований один із цих patterns. Це несучі принципи production bot-а. Пропустіть їх - і знайдете їх знову у власних postmortems.

Поширені запитання

Яка найкоштовніша помилка Polymarket bot?
Вийти в live до того, як paper-trading досягне 30-trade gate. Ми так робили. Помилка не лише в тому, щоб втратити гроші - а й у тому, що ви втрачаєте шанс навчитися на strategy в контрольованому середовищі. Bots, які виходять у live занадто рано, або жорстко знищуються і покидаються, або місяцями відновлюються перед повторним paper-trading.
Що таке phantom fill bug?
Коли bot вважає, що order filled, але exchange записав його як такий, що ще не filled. Симптоми: position є в state bot-а, але не on-chain, що призводить до double-orders під час retry. Виправлено в нашому trader-і через три commits (e68a087, 8bb7761, 06deaef): використовувати FOK для buys, опитувати status, доки не буде matched, ніколи не вважати status=delayed як filled.
Що таке інцидент lol-ctg-ccg whipsaw?
Esports market із тонким order book, де наш trader відкрив -$2.55 stop-loss на 0.14, а потім побачив, як ціна відновилася до 0.325 менш ніж за 2 minutes. Ми налаштували stop-loss на -4 percentage points, що занадто тісно для thin esports books. Fix: розширили SL до -8pp для low-liquidity markets, а тісніший SL залишили лише для thick books (NBA, high-liquidity soccer). Див. memory/trader-sl-wider.md.
Як проявився NegRisk flag bug?
Bot виставляв orders без встановлення neg_risk=true на multi-outcome markets. Orders відхилялися з заплутаними повідомленнями про помилку, що спричиняло multi-second затримки перед retry і, як наслідок, пропущені fills. Fix у commit 06deaef: завжди встановлювати neg_risk відповідно до market metadata, ніколи не припускати.
Що було в інциденті sleep-through-bug?
Wallet застряг із stuck order о 4am. Owner наказав bot-у зупинитися; торкнулися файла data/halt_autobuy. Bot виявив файл перед наступною спробою торгівлі й відмовився виставляти orders. Owner прокинувся до чистого state, а не гіршого. Патерн halt-sentinel було підтверджено; тепер ми вбудовуємо його за замовчуванням у кожен bot.
Який один урок із цих postmortems є найбільш узагальнюваним?
Ніколи не довіряйте happy path. Кожен баг, який ми випустили, стався через припущення, що request успішний, fill реальний або ціна не рухатиметься. Пишіть code defensively: припускайте, що orders fail, що reconciliations diverge, що один market ось-ось зробить щось дивне. Параноя коштує небагато; ціна її пропуску - postmortem, який ви напишете пізніше.