Polymarket Bot Tutorial · Глава 32 из 32

Реальные ошибки Polymarket bot и postmortem: phantom fills, sticky-fail dedup, lol-ctg-ccg whipsaw, баг флага NegRisk, premature go-live - с commits и датами, которые исправили каждую из них.

Что покрывает эта глава

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

  • Phantom fills (commits e68a087, 8bb7761)
  • Баг флага NegRisk (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 buy, все были matched на CLOB. Сразу после этого bot попытался разместить 22 GTC sell. 8 из них были rejected с сообщением "balance: 0 / sum of active orders: 0 / order amount: 10000000."

Root cause: settlement lag (chapter 12). CLOB matched за 100ms, bot отправил sell через 200ms, но transfer Polygon ERC-1155 занял примерно 2 секунды. CLOB отклонил sell, потому что chain по-прежнему показывал нулевой balance.

Fix: вставить blocking wait на 5 секунд между любым успешным buy и любым последующим GTC на том же token. Commits e68a087 и 8bb7761. С тех пор - zero phantom-fill incidents.

Lesson: API time и chain time - это разные timelines. Code, который предполагает, что они синхронны, столкнется именно с этим failure mode.

Баг флага NegRisk (commit 06deaef)

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

Root cause: bot вызывал createAndPostOrder, не устанавливая negRisk: true в object flags. У двух рынков была другая historical creation date, и им требовался этот flag; шести он не был нужен, потому что их underlying contract уже маршрутизировался через NegRisk по умолчанию.

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

Lesson: никогда не ставьте property рынка по умолчанию. Каждый раз читайте его явно из source of truth.

Sticky-fail dedup (commit 4c0bef1)

Bot повторил неудачный buy 5 раз за 12 секунд. Первая попытка на самом деле succeeded (network timeout не дал bot'у увидеть response); следующие 4 retries создали еще 4 additional positions. Итого: 5 positions на одном и том же market, когда нам была нужна 1.

Root cause: отсутствовал idempotent client-order-id. Retry logic у bot'а была такой: "если не получилось, попробуй снова с новым salt." У CLOB не было способа распознать retries как duplicates.

Fix: генерировать deterministic UUID для каждого intended order до первой попытки. Все retries используют один и тот же client-order-id, что позволяет CLOB делать dedup. Commit 4c0bef1.

Lesson: retries без idempotence - это duplicates. У каждого order должен быть стабильный client-side identifier.

Whipsaw incident: lol-ctg-ccg

В esports match (CTG vs CCG) bot вошел в buy по 0.45, когда imbalance стал positive. В течение 30 секунд imbalance стал negative, и наш GTC sell по 0.50 был исполнен чьим-то чужим order. PnL: +5c × 10 shares = +$0.50.

Через 10 минут imbalance на том же market снова стал positive. Bot снова вошел по 0.42. На этот раз imbalance так и не восстановился; mid drifted до 0.18, и position дотянулась до resolution на 0.

Root cause: strategy трактовала imbalance как directional signal, но не отслеживала, что imbalance bouncing - оба signal были noise, а не information. Bot был whipsawed между двумя ложными signal на одном и том же market в течение 20 минут.

Fix: cooldown per market - после fill новые entries на том же market запрещены в течение 30 минут. Это позволяло multiple entries на разных markets, но не back-to-back на одном и том же.

Lesson: signal, который bounce'ится, - это не signal. Перед действием фильтруйте по persistence.

Premature go-live: wipe 2025 года

Новая market-making strategy прошла 12 paper trades. Builder не дождался 30, решил, что "все выглядит хорошо", и выкатил live с $500 капитала. Через 18 часов wallet был уже на $200.

Root cause: 12 trades - недостаточная выборка, чтобы отличить 60% WR от 35% WR. На самом деле strategy давала 35% WR; в 12-trade paper window просто попалась нерепрезентативная streak.

30-trade gate существует не просто так. Variance на sample из 12 trades делает его неотличимым от "strategy не работает".

Lesson: discipline beats conviction. 30-trade gate не обсуждается.

Sleep-through-bug: kill switch сработал

У bot'а был off-by-one в time-of-day filter - он должен был ставить pause в 02:00 UTC, а на деле ставил его в 03:00 UTC. В течение часа 02:00-03:00 без pause Polygon RPC сильно rate-limit'ил наши requests; read path bot'а возвращал stale data.

Bot продолжал торговать по stale prices. PnL за час: -$3.20 по 22 trades. Daily-loss kill switch сработал на -5%, остановил bot и отправил Telegram alert в 03:08 UTC. Builder проснулся и увидел halted bot в 09:00; total damage ограничился kill threshold.

Lesson: баг был real, но kill switch сработал. -$3.20 вместо -$50.00. Risk controls не предотвращают баги; они ограничивают cost тех багов, которых вы не увидели заранее.

Уроки, которые обобщаются

Во всех postmortem повторяются четыре pattern.

  1. API time ≠ chain time. Settlement lag, RPC lag, WebSocket lag - все это создает gaps, которые bot code должен явно обрабатывать.
  2. Retries need idempotence. Retry без client-order-id - это риск duplicate order. Всегда.
  3. Read every market property explicitly. NegRisk flag, tick size, expiration. Никогда не default'ите; всегда читайте из source of truth.
  4. Kill switch - это floor, а не feature. Risk controls ограничивают losses на bugs. Strategy не предотвращают bugs; они предполагают, что bot работает корректно. Bot не всегда будет работать корректно.

В каждой главе этой серии где-то встроен один из этих pattern. Это несущие principles production bot. Пропустите их - и вы снова найдете их в собственных postmortem.

Часто задаваемые вопросы

Какая самая дорогая ошибка Polymarket bot?
Выход в live до того, как paper-trading пройдет 30-trade gate. Мы это делали. Ошибка не только в потере денег - вы теряете шанс учиться на strategy в controlled environment. Bot'ы, которые выходят в live слишком рано, либо получают nuked и забрасываются, либо месяцами теряют время на восстановление перед повторным paper-trading.
Что такое phantom fill bug?
Когда bot считает, что order filled, а exchange записал его как еще не filled. Симптомы: position появляется в state bot'а, но не on-chain, что приводит к double orders при retry. В нашем trader исправлено тремя commits (e68a087, 8bb7761, 06deaef): использовать FOK для buy, опрашивать status до matched, никогда не считать status=delayed как filled.
Что такое lol-ctg-ccg whipsaw incident?
Эта esports market на thin order book, где наш trader сработал по stop-loss -$2.55 на 0.14, а затем увидел, как цена восстановилась до 0.325 в течение 2 минут. Мы настроили stop-loss на -4 percentage points, что слишком узко для thin esports books. Fix: расширили SL до -8pp для low-liquidity markets, а более tight SL оставили только для thick books (NBA, high-liquidity soccer). См. memory/trader-sl-wider.md.
Как проявился баг флага NegRisk?
Bot размещал orders без установки neg_risk=true в multi-outcome markets. Orders отклонялись с запутанными error messages, что приводило к задержкам в несколько секунд перед retry и, как следствие, к пропущенным fills. Fix в commit 06deaef: всегда задавайте neg_risk в соответствии с market metadata, никогда не предполагайте.
Что произошло в incident sleep-through-bug?
Wallet завис с stuck order в 4 утра. Владелец приказал bot'у остановиться; коснулся файла data/halt_autobuy. Bot обнаружил файл перед следующей попыткой trade и отказался размещать orders. Владелец проснулся и увидел чистое состояние вместо худшего. Pattern halt-sentinel был подтвержден; теперь мы поставляем его по умолчанию в каждом bot'е.
Какой один урок из этих postmortem наиболее обобщаем?
Никогда не доверяйте happy path. Каждый баг, который мы shipped, возник из предположения, что request succeeded, fill был real или price не сдвинется. Пишите code defensively: считайте, что orders fail, reconciliations diverge, а один market вот-вот сделает что-то странное. Плата за paranoia мала; цена отказа от нее - postmortem, который вы напишете позже.