آموزش Polymarket Bot · فصل 32 از 32

اشتباهات واقعی Polymarket bot و postmortemها: phantom fills، sticky-fail dedup، whipsaw در lol-ctg-ccg، باگ NegRisk flag، go-live زودهنگام - همراه با commitها و تاریخ‌هایی که هرکدام را برطرف کردند.

این فصل چه چیزهایی را پوشش می‌دهد

دفترچه تولیدی خودمان از bugهایی که پول واقعی از بین بردند. الگوها از جزئیات مهم‌ترند - همان دسته‌های bug در botهای مختلف تکرار می‌شوند، و درمان معمولاً یک watchdogِ جاافتاده است، نه یک strategy بهتر. هدف این فصل این است که شهریه شما را پس بگیرد.

  • Phantom fills (commitهای 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 کار کرد
  • درس‌هایی که قابل تعمیم‌اند

Phantom fills (commitهای e68a087، 8bb7761)

اولین incident بزرگ phantom-fill در trader ما، مه 2025. Bot 22 خرید FOK ثبت کرد که همگی در CLOB matched شدند. Bot بلافاصله تلاش کرد 22 فروش GTC ثبت کند. 8 مورد با پیام "balance: 0 / sum of active orders: 0 / order amount: 10000000." رد شدند.

علت اصلی: settlement lag (فصل 12). CLOB در 100ms match شد، bot فروش را در 200ms ثبت کرد، اما انتقال Polygon ERC-1155 حدود 2 ثانیه طول کشید. CLOB فروش را رد کرد چون chain هنوز balance صفر را نشان می‌داد.

رفع: بین هر خرید موفق و هر follow-up از نوع GTC روی همان token، یک انتظار blocking پنج‌ثانیه‌ای اضافه کنید. commitهای e68a087 و 8bb7761. از آن زمان هیچ incident phantom-fill نداشته‌ایم.

درس: زمان API و زمان chain دو timeline متفاوت هستند. کدی که فرض کند هم‌زمان‌اند، دقیقاً با همین failure mode روبه‌رو می‌شود.

NegRisk flag bug (commit 06deaef)

یک event چند-نتیجه‌ای NegRisk با 8 candidate یک arb موقت 1.8c داشت (جمع YES askها = 0.982). arber ما هر 8 خرید FOK را اجرا کرد. 6 تای آن‌ها filled شدند؛ 2 تای دیگر در exchange contract اشتباهی settled شدند.

علت اصلی: bot تابع createAndPostOrder را بدون تنظیم negRisk: true در object فلگ‌ها صدا می‌زد. دو تا از بازارها تاریخ ایجاد تاریخی متفاوتی داشتند و به این flag نیاز داشتند؛ شش مورد دیگر نیازی نداشتند چون contract زیرین آن‌ها از قبل به‌صورت پیش‌فرض از طریق NegRisk routing می‌شد.

رفع: در Gamma برای هر market، market.negRisk را بخوانید و به هر order call منتقل کنید. commit 06deaef. ما arb را با flag فعال دوباره اجرا کردیم؛ 2 مورد باقی‌مانده درست settled شدند.

درس: هیچ property مربوط به market را به‌صورت پیش‌فرض فرض نکنید. هر بار آن را صریحاً از source of truth بخوانید.

Sticky-fail dedup (commit 4c0bef1)

Bot یک خرید ناموفق را در 12 ثانیه 5 بار retry کرد. تلاش اول در واقع موفق شده بود (timeout شبکه باعث شده بود bot پاسخ را نبیند)؛ 4 retry بعدی 4 position اضافی ایجاد کردند. مجموع: 5 position در همان market وقتی فقط 1 تا می‌خواستیم.

علت اصلی: نبود client-order-id اتمیک. منطق retry در bot این بود: "اگر fail شد، با یک salt جدید دوباره امتحان کن." CLOB هیچ راهی برای تشخیص retryها به‌عنوان duplicate نداشت.

رفع: قبل از اولین تلاش، یک UUID deterministic برای هر order موردنظر تولید کنید. همه retryها از همان client-order-id استفاده می‌کنند و به CLOB اجازه می‌دهند duplicateها را dedup کند. commit 4c0bef1.

درس: retry بدون idempotence یعنی duplicate. هر order به یک شناسه client-side پایدار نیاز دارد.

Whipsaw incident: lol-ctg-ccg

یک match در esports (CTG در برابر CCG) باعث شد bot وقتی imbalance مثبت شد، در 0.45 وارد buy شود. ظرف 30 ثانیه، imbalance منفی شد و فروش GTC ما در 0.50 توسط order شخص دیگری hit شد. PnL: +5c × 10 shares = +$0.50.

10 دقیقه بعد، imbalance همان market دوباره مثبت شد. Bot دوباره در 0.42 وارد شد. این بار imbalance هیچ‌وقت برنگشت؛ mid تا 0.18 drift کرد و position تا resolution در 0 ماند.

علت اصلی: strategy از imbalance به‌عنوان signal جهت‌دار استفاده می‌کرد، اما دنبال نمی‌کرد که imbalance دارد bounce می‌زند - هر دو signal فقط noise بودند، نه information. Bot در عرض 20 دقیقه روی دو signal ناموفق در همان market whipsaw شد.

رفع: cooldown برای هر market - بعد از هر fill، تا 30 دقیقه هیچ entry جدیدی روی همان market نباشد. این کار اجازه می‌داد روی marketهای مختلف چندین entry انجام شود، اما نه پشت‌سرهم روی همان یکی.

درس: سیگنالی که bounce می‌زند، signal نیست. قبل از اقدام، persistence را فیلتر کنید.

Premature go-live: 2025 wipe

یک strategy جدید market-making از 12 paper trade عبور کرد. سازنده منتظر 30 مورد نماند، گفت "خوب به نظر می‌رسد"، و با $500 سرمایه به‌صورت live deploy کرد. ظرف 18 ساعت wallet به $200 رسید.

علت اصلی: 12 trade برای تشخیص WR 60% از WR 35% sample کافی نیست. آن strategy در واقع WR 35% داشت؛ بازه 12-trade در paper اتفاقاً یک streak غیرنماینده داشت.

دروازه 30-trade بی‌دلیل وجود ندارد. variance روی یک sample 12-trade آن را از "این strategy کار نمی‌کند" غیرقابل‌تشخیص می‌کند.

درس: انضباط از conviction مهم‌تر است. دروازه 30-trade قابل مذاکره نیست.

Sleep-through-bug: kill switch کار کرد

Bot در time-of-day filter خود یک off-by-one داشت - قرار بود ساعت 02:00 UTC pause کند، اما در واقع در 03:00 UTC pause می‌کرد. در ساعت 02:00 تا 03:00 که pause نشده بود، Polygon RPC درخواست‌های ما را به‌شدت rate-limit می‌کرد؛ مسیر read در bot داده stale برمی‌گرداند.

Bot با قیمت‌های stale به معامله ادامه داد. PnL آن ساعت: -$3.20 در 22 trade. kill switch مربوط به daily-loss در -5% فعال شد، bot را متوقف کرد و در 03:08 UTC یک هشدار Telegram فرستاد. سازنده ساعت 09:00 با یک bot متوقف‌شده از خواب بیدار شد؛ خسارت کل فقط تا آستانه kill محدود شد.

درس: bug واقعی بود، اما kill switch کار کرد. به‌جای -$50.00 فقط -$3.20. کنترل‌های risk مانع bug نمی‌شوند؛ هزینه bugهایی را که ندیده‌اید محدود می‌کنند.

درس‌هایی که قابل تعمیم‌اند

در همه postmortemها، چهار الگو تکرار می‌شوند.

  1. زمان API ≠ زمان chain. settlement lag، RPC lag، WebSocket lag - همه شکاف‌هایی ایجاد می‌کنند که کد bot باید صریحاً با آن‌ها برخورد کند.
  2. retryها به idempotence نیاز دارند. retry بدون client-order-id یعنی ریسک duplicate order. همیشه.
  3. همه propertyهای market را صریح بخوانید. NegRisk flag، tick size، expiration. هرگز default نکنید؛ همیشه از source of truth بخوانید.
  4. kill switch کفِ خسارت است، نه یک feature. کنترل‌های risk خسارت bugها را محدود می‌کنند. strategyها bug را از بین نمی‌برند؛ فرض می‌کنند bot درست کار می‌کند. bot همیشه درست کار نخواهد کرد.

هر فصل در این مجموعه جایی یکی از این الگوها را در خود دارد. این‌ها اصول load-bearing یک bot production هستند. از آن‌ها عبور کنید و دوباره در postmortemهای خودتان پیدایشان می‌کنید.

سؤالات متداول

گران‌ترین اشتباه Polymarket bot چیست؟
live کردن قبل از اینکه paper-trading به دروازه 30-trade برسد. ما این کار را کرده‌ایم. اشتباه فقط از دست دادن پول نیست - از دست دادن فرصت یادگیری از strategy در یک محیط کنترل‌شده هم هست. botهایی که خیلی زود live می‌شوند یا نابود و رها می‌شوند، یا ماه‌ها را صرف بازیابی می‌کنند قبل از اینکه دوباره paper-trading کنند.
phantom fill bug چیست؟
وقتی bot فکر می‌کند order filled شده، اما exchange آن را هنوز filled نشده ثبت کرده است. نشانه‌ها: position در state bot ظاهر می‌شود اما روی chain نیست، و در retry باعث double-order می‌شود. در trader ما با سه commit (e68a087، 8bb7761، 06deaef) رفع شد: برای buyها از FOK استفاده کنید، status را تا زمان match شدن poll کنید، و هرگز status=delayed را معادل filled فرض نکنید.
incident whipsaw در lol-ctg-ccg چه بود؟
یک market esports با order book نازک که trader ما یک stop-loss با -$2.55 در 0.14 اجرا کرد، و بعد دید قیمت ظرف 2 دقیقه تا 0.325 recovery کرد. ما stop-loss را روی -4 percentage points تنظیم کرده بودیم که برای order bookهای نازک esports خیلی تنگ است. رفع: SL برای بازارهای کم‌نقدشونده به -8pp گسترش داده شد، و SL تنگ‌تر فقط برای bookهای ضخیم (NBA، soccer با نقدشوندگی بالا) نگه داشته شد. به memory/trader-sl-wider.md مراجعه کنید.
باگ NegRisk flag چگونه ظاهر شد؟
Bot بدون تنظیم neg_risk=true روی marketهای چندنتیجه‌ای order ثبت می‌کرد. orderها با پیام‌های خطای گیج‌کننده رد می‌شدند، که باعث تأخیر چندثانیه‌ای قبل از retry و در نتیجه از دست رفتن fills می‌شد. رفع در commit 06deaef: همیشه neg_risk را بر اساس metadata هر market تنظیم کنید، هرگز فرض نکنید.
incident sleep-through-bug چه بود؟
Wallet ساعت 4 صبح با یک order گیرکرده قفل شد. صاحب bot را برای halt کردن راهنمایی کرد؛ فایل data/halt_autobuy را لمس کرد. Bot پیش از تلاش بعدی برای معامله فایل را تشخیص داد و از ثبت order خودداری کرد. صاحب صبح با یک state تمیز بیدار شد، نه بدتر. الگوی halt-sentinel تأیید شد؛ حالا آن را به‌صورت پیش‌فرض در هر bot منتشر می‌کنیم.
تعمیم‌پذیرترین درس واحد از این postmortemها چیست؟
هرگز به مسیر happy path اعتماد نکنید. هر bugی که ما منتشر کرده‌ایم از این فرض آمده که یک request موفق شده، یک fill واقعی بوده، یا یک قیمت حرکت نخواهد کرد. دفاعی کدنویسی کنید: فرض کنید orderها fail می‌شوند، فرض کنید reconciliationها diverge می‌شوند، فرض کنید یک market قرار است کار عجیبی انجام دهد. مالیات paranoia کوچک است؛ هزینه نپرداختن آن همان postmortemی است که بعداً می‌نویسید.