Polymarket Bot Tutorial · Chương 32 trong 32

Những sai lầm thực tế của Polymarket bot và postmortem: phantom fills, sticky-fail dedup, lol-ctg-ccg whipsaw, NegRisk flag bug, go-live quá sớm - cùng các commit và ngày đã sửa từng lỗi.

Chương này bao gồm gì

Nhật ký production của chính chúng tôi về những bug đã làm mất tiền thật. Mẫu hình quan trọng hơn chi tiết cụ thể - cùng một lớp bug sẽ lặp lại trên nhiều bot, và cách chữa thường là thiếu một watchdog, chứ không phải một strategy tốt hơn. Chương này nhằm giúp bạn tiết kiệm học phí đó.

  • Phantom fills (commits e68a087, 8bb7761)
  • NegRisk flag bug (commit 06deaef)
  • Sticky-fail dedup (commit 4c0bef1)
  • Whipsaw incident: lol-ctg-ccg
  • Go-live quá sớm: 2025 wipe
  • Sleep-through-bug: kill switch đã hoạt động
  • Bài học có thể khái quát hóa

Phantom fills (commits e68a087, 8bb7761)

Sự cố phantom-fill lớn đầu tiên trên trader của chúng tôi, tháng 5 năm 2025. Bot đặt 22 lệnh mua FOK, tất cả đều được match trên CLOB. Sau đó bot ngay lập tức cố gắng đặt 22 lệnh bán GTC. 8 lệnh trong số đó bị từ chối với lỗi "balance: 0 / sum of active orders: 0 / order amount: 10000000."

Nguyên nhân gốc: settlement lag (chương 12). CLOB match trong 100ms, bot đặt lệnh bán sau 200ms, nhưng việc chuyển Polygon ERC-1155 mất khoảng 2 giây. CLOB từ chối lệnh bán vì chain vẫn hiển thị số dư bằng 0.

Cách sửa: chèn một khoảng chờ chặn 5 giây giữa bất kỳ lệnh mua thành công nào và lệnh GTC theo sau trên cùng token. Commits e68a087 và 8bb7761. Không còn sự cố phantom-fill nào kể từ đó.

Bài học: thời gian của API và thời gian của chain là hai dòng thời gian khác nhau. Code giả định chúng đồng bộ sẽ gặp đúng kiểu lỗi này.

NegRisk flag bug (commit 06deaef)

Một sự kiện NegRisk multi-outcome có 8 candidate đã xuất hiện một arb nhất thời là 1.8c (tổng YES asks = 0.982). Bộ arber của chúng tôi đã bắn cả 8 lệnh mua FOK. 6 lệnh được fill; 2 lệnh được settle vào sai exchange contract.

Nguyên nhân gốc: bot đang gọi createAndPostOrder mà không đặt negRisk: true trong flags object. Hai market có ngày tạo lịch sử khác và cần flag này; sáu market còn lại không cần vì underlying contract của chúng đã route qua NegRisk mặc định.

Cách sửa: đọc market.negRisk từ Gamma cho từng market, rồi truyền qua mọi lệnh gọi order. Commit 06deaef. Chúng tôi chạy lại arb với flag được bật; 2 lệnh còn lại được settle đúng.

Bài học: đừng bao giờ default một market property. Hãy đọc nó một cách rõ ràng từ source of truth mỗi lần.

Sticky-fail dedup (commit 4c0bef1)

Bot đã thử lại một lệnh mua thất bại 5 lần trong 12 giây. Lần thử đầu tiên thực ra đã thành công (network timeout khiến bot không thấy response); 4 lần retry tiếp theo tạo thêm 4 position nữa. Tổng cộng: 5 position trên cùng một market trong khi chúng tôi chỉ muốn 1.

Nguyên nhân gốc: không có client-order-id idempotent. Logic retry của bot là "nếu thất bại thì thử lại với salt mới." CLOB không có cách nào nhận ra các lần retry là bản trùng lặp.

Cách sửa: tạo một UUID có tính xác định cho mỗi order dự định trước lần thử đầu tiên. Tất cả các lần retry dùng cùng một client-order-id, cho phép CLOB dedup. Commit 4c0bef1.

Bài học: retry mà không có idempotence thì sẽ thành duplicate. Mỗi order cần một định danh ổn định ở phía client.

Whipsaw incident: lol-ctg-ccg

Một trận esports (CTG vs CCG) khiến bot vào lệnh mua ở 0.45 khi imbalance chuyển sang dương. Trong vòng 30 giây, imbalance chuyển sang âm và lệnh bán GTC của chúng tôi ở 0.50 đã bị lệnh của người khác khớp. PnL: +5c × 10 cổ phiếu = +$0.50.

10 phút sau, imbalance của cùng market lại chuyển sang dương. Bot vào lệnh lại ở 0.42. Lần này imbalance không bao giờ hồi phục; mid trôi xuống 0.18 và position đi đến lúc resolution ở 0.

Nguyên nhân gốc: strategy coi imbalance là tín hiệu xu hướng nhưng không theo dõi việc imbalance đang bật qua bật lại - cả hai tín hiệu đều là noise, không phải thông tin. Bot bị whipsaw qua hai tín hiệu thất bại trên cùng một market trong vòng 20 phút.

Cách sửa: cooldown theo từng market - sau khi fill, không cho vào lệnh mới trên cùng market trong 30 phút. Vẫn cho phép vào nhiều market khác nhau, nhưng không vào liên tiếp trên cùng một market.

Bài học: một tín hiệu bật qua bật lại thì không phải là tín hiệu. Hãy lọc tính bền vững trước khi hành động.

Go-live quá sớm: 2025 wipe

Một strategy market-making mới đã vượt qua 12 paper trade. Người xây bot không chờ đến 30, quyết định "trông ổn", rồi deploy live với $500 capital. Trong vòng 18 giờ, wallet còn $200.

Nguyên nhân gốc: 12 trade là chưa đủ mẫu để phân biệt WR 60% với WR 35%. Strategy thực tế chỉ có WR 35%; cửa sổ paper 12 trade chỉ vô tình có một chuỗi không đại diện.

Ngưỡng 30 trade tồn tại là có lý do. Độ biến thiên trên mẫu 12 trade khiến nó không thể phân biệt với "strategy không hoạt động."

Bài học: kỷ luật quan trọng hơn niềm tin. Ngưỡng 30 trade là không thể thương lượng.

Sleep-through-bug: kill switch đã hoạt động

Bot có lỗi off-by-one trong time-of-day filter - đáng lẽ phải tạm dừng lúc 02:00 UTC nhưng thực tế lại tạm dừng lúc 03:00 UTC. Trong giờ 02:00-03:00 chưa bị tạm dừng, Polygon RPC rate-limit các request của chúng tôi rất mạnh; đường đọc của bot trả về dữ liệu cũ.

Bot tiếp tục giao dịch dựa trên giá stale. PnL trong giờ đó: -$3.20 qua 22 trade. Daily-loss kill switch kích hoạt ở mức -5%, dừng bot, gửi Telegram alert lúc 03:08 UTC. Người xây bot thức dậy lúc 09:00 và thấy bot đã bị dừng, tổng thiệt hại chỉ giới hạn ở ngưỡng kill.

Bài học: bug là có thật nhưng kill switch đã hoạt động. -$3.20 thay vì -$50.00. Risk controls không ngăn bug; chúng giới hạn chi phí của những bug bạn không lường trước được.

Bài học có thể khái quát hóa

Trong tất cả postmortem, có bốn mẫu hình lặp lại.

  1. API time ≠ chain time. Settlement lag, RPC lag, WebSocket lag - tất cả đều tạo ra khoảng trống mà code bot phải xử lý rõ ràng.
  2. Retries cần idempotence. Retry mà không có client-order-id thì luôn có nguy cơ tạo duplicate order.
  3. Đọc mọi market property một cách rõ ràng. NegRisk flag, tick size, expiration. Đừng bao giờ default; hãy luôn đọc từ source of truth.
  4. Kill switch là sàn, không phải tính năng. Risk controls giới hạn thua lỗ khi có bug. Strategy không ngăn bug; strategy giả định bot hoạt động đúng. Bot sẽ không luôn luôn hoạt động đúng.

Mỗi chương trong series này đều có một trong những mẫu hình này ở đâu đó bên trong. Chúng là những nguyên tắc chịu tải của một production bot. Bỏ qua chúng, và bạn sẽ gặp lại chúng trong các postmortem của chính mình.

Các câu hỏi thường gặp

Sai lầm đắt nhất của Polymarket bot là gì?
Go live trước khi paper-trading đạt ngưỡng 30 trade. Chúng tôi đã làm rồi. Sai lầm không chỉ là mất tiền - mà còn là mất cơ hội học từ strategy trong một môi trường kiểm soát. Những bot go live quá sớm либо bị quét sạch và bỏ luôn, либо mất nhiều tháng để hồi phục trước khi paper-trading lại.
Phantom fill bug là gì?
Khi bot tin rằng một order đã fill nhưng exchange lại ghi nhận là chưa fill. Triệu chứng: position xuất hiện trong state của bot nhưng không có trên-chain, dẫn đến đặt lệnh kép khi retry. Đã được sửa trong trader của chúng tôi qua ba commit (e68a087, 8bb7761, 06deaef): dùng FOK cho lệnh mua, poll status cho đến khi matched, không bao giờ tin status=delayed là đã fill.
Sự cố whipsaw lol-ctg-ccg là gì?
Một market esports trên sổ lệnh mỏng nơi trader của chúng tôi bắn stop-loss -$2.55 tại 0.14, rồi thấy giá hồi lên 0.325 trong vòng 2 phút. Chúng tôi đã cấu hình stop-loss ở mức -4 percentage points, quá chặt cho sổ lệnh esports mỏng. Cách sửa: nới SL lên -8pp cho market thanh khoản thấp, chỉ giữ SL chặt hơn cho các sổ lệnh dày (NBA, soccer thanh khoản cao). Xem memory/trader-sl-wider.md.
Bug NegRisk flag biểu hiện như thế nào?
Bot đặt lệnh mà không set neg_risk=true trên các market multi-outcome. Lệnh bị từ chối với thông báo lỗi khó hiểu, dẫn đến chậm vài giây trước khi retry, dẫn đến bỏ lỡ các lần fill. Sửa trong commit 06deaef: luôn set neg_risk theo metadata của market, không bao giờ tự đoán.
Sự cố sleep-through-bug là gì?
Wallet bị kẹt với một order bị treo lúc 4 giờ sáng. Chủ sở hữu yêu cầu bot dừng; chạm vào file data/halt_autobuy. Bot phát hiện file đó trước lần thử đặt lệnh tiếp theo và từ chối đặt order. Chủ sở hữu thức dậy thấy trạng thái sạch sẽ thay vì tệ hơn. Đã xác nhận pattern halt-sentinel; giờ chúng tôi ship nó mặc định trong mọi bot.
Bài học đơn lẻ có tính khái quát nhất từ các postmortem này là gì?
Đừng bao giờ tin happy path. Mọi bug chúng tôi đã ship đều bắt nguồn từ việc giả định một request đã thành công, một fill là thật, hoặc giá sẽ không di chuyển. Hãy code phòng thủ: giả định order có thể thất bại, giả định reconciliations có thể lệch nhau, giả định một market sắp làm điều gì đó kỳ quặc. Cái giá của sự đa nghi là nhỏ; cái giá của việc bỏ qua nó là postmortem bạn sẽ phải viết sau này.