Polymarket Bot Tutorial · Chapter 12 of 32
Polymarket phantom fills কীভাবে detect করতে হয় (যে order filled দেখায় কিন্তু আসলে নয়), idempotent retries implement করতে হয়, status=matched আর rested-on-book-এর মধ্যে পার্থক্য করতে হয়, এবং transient failures থেকে কীভাবে টিকে থাকতে হয়।
এই chapter-এ কী covered হয়েছে
Phantom fill হলো Polymarket-specific এক ধরনের failure mode, যেখানে CLOB match acknowledge করে কিন্তু chain এখনও ERC-1155 transfer confirm করেনি। এরপর প্রায় ~5 seconds-এর মধ্যে আরেকটা order দিলে misleading "balance: 0" error দিয়ে reject হয়। এর সমাধান হলো idempotence আর settlement wait। এই chapter-এ production playbook আছে, যেটার জন্য আমরা বাস্তব টাকা খরচ করেছি।
- Phantom fill কী
- status=matched বনাম status=delayed বনাম status=posted
- Polling pattern: celebrate করার আগে status poll করুন
- FOK as anti-phantom-fill
- client_order_id দিয়ে idempotent retries
- Real production incident: আমরা কীভাবে ours fix করেছি
- Code: detect-then-act post-order pattern
Phantom fill কী
Phantom fill হলো যখন CLOB API আপনার order-এর জবাবে status: "matched" দেয়, কিন্তু on-chain ERC-1155 transfer এখনও settle হয়নি। CLOB matcher Polygon block production-এর চেয়ে দ্রুত (~2s per block)। API match-এর পর প্রায় 2-5 seconds ধরে আপনার wallet on-chain এ সেই tokens ধরে না, যেগুলো matcher বলে আপনি own করেন।
Bot-এর bug তখনই প্রকাশ পায়, যখন follow-up action - সাধারণত take-profit post করার জন্য একটি GTC sell - ওই window-এর মধ্যে চালানো হয়। তখন CLOB chain balance check করে, zero দেখে, এবং not enough balance / allowance: balance: 0, order amount: N দিয়ে reject করে। Error message-এ allowance-কে দোষ দেওয়া হয়; আসল কারণ settlement lag।
প্রথমবার এটা ঘটলে আপনি allowance bug ধরে নিয়ে এক ঘণ্টা নষ্ট করবেন। সমাধান খুবই সহজ: wait করুন, verify করুন, তারপর post করুন।
status=matched বনাম status=delayed বনাম status=posted
Order placement response-এ একটি status field থাকে, যার তিনটি value গুরুত্বপূর্ণ।
matched: orderটি সঙ্গে সঙ্গে book-এর সাথে match হয়েছে। Inventory 2-5 seconds-এর মধ্যে settle হবে। সফল হলে FOK/FAK এটাই return করে।delayed: matcher synchronousভাবে settle করতে পারেনি এবং match queue-তে রেখেছে। বিরল; সাধারণত congestion বোঝায়। wait + verify pattern-এর জন্য এটিকেmatched-এর মতোই treat করুন।posted(এটিকেlive-ও বলা হয়): orderটি book-এ resting আছে, fill হয়নি। GTC order থেকে আসে যা সঙ্গে সঙ্গে match হয়নি। Inventory অপরিবর্তিত থাকে; এখনও কোনো follow-up action দরকার নেই।
Decision rule: status যদি matched বা delayed হয়, তাহলে chain transfer verify না করা পর্যন্ত নতুন inventory-র ওপর নির্ভর করে এমন কোনো follow-up দেবেন না।
Polling pattern: celebrate করার আগে status poll করুন
Verification pattern: successful match-এর পর CTF balance poll করুন যতক্ষণ না নতুন tokens reflect করে, তারপর এগোন।
def wait_for_settlement(token_id, expected_size, timeout=15):
"""Block until on-chain balance reaches expected_size or timeout."""
start = time.time()
while time.time() - start < timeout:
bal = ctf_contract.functions.balanceOf(PROXY, token_id).call()
if bal >= expected_size:
return True
time.sleep(0.5)
return False
সাধারণ settlement: ভালো network conditions-এ 2-5 seconds, Polygon congestion-এর সময় 15s পর্যন্ত। 5-second wait 95% case cover করে; production-এ timeout 15s রাখুন এবং timeout হলে alert দিন।
যেসব high-frequency bot block করতে পারে না, তাদের জন্য একটি alternative হলো event-subscription: আপনার proxy address-এর জন্য CTF-এর TransferSingle event watch করুন এবং receipt-এর পর downstream action trigger করুন। এতে strategy loop block না হয়ে wait queue-তে push হয়।
FOK as anti-phantom-fill
FAK-এর বদলে FOK বেছে নেওয়া phantom-fill chaos-এর বিরুদ্ধে আংশিক defense। FOK হয় পুরো order fill করে, নয়তো cancelled return করে; FAK partial হলে filled_size return করতে পারে। Partial fill-এর পর যদি original order size অনুযায়ী GTC sell দেওয়া হয়, তাহলে settlement-lag আর size mismatch - এই দুই bug একসাথে কাজ করে - আর sell fail হয়।
FOK-এ size binary: হয় full size match হয়েছে, নয়তো একদম হয়নি। তাই follow-up posting logic সবসময় জানে কী expect করতে হবে।
এতে wait-এর প্রয়োজন শেষ হয় না - perfect FOK match-ও 2-5 second settlement window-এর অধীন। তবে এটি bookkeeping divergence-এর একটি class সরিয়ে দেয়।
client_order_id দিয়ে idempotent retries
Order placement চলাকালীন network failure worst-case scenario তৈরি করে: bot-এর HTTP call timeout হয়েছে, কিন্তু order পৌঁছেছে কি না বোঝা যাচ্ছে না। অন্ধভাবে retry করলে double-place হতে পারে; retry না করলে position হারাতে পারেন।
সমাধান হলো order placement-এ client_order_id field ব্যবহার করা। Intended প্রতিটি order-এর জন্য deterministic UUID generate করুন; server যদি আগেই সেই ID দেখে থাকে, তাহলে duplicate তৈরি না করে existing order-এর status return করবে।
import uuid
oid = str(uuid.uuid4()) # generate once, retry with same value
for attempt in range(3):
try:
resp = c.create_and_post_order(args, OrderType.FOK, client_order_id=oid)
return resp
except (TimeoutError, ConnectionError):
time.sleep(0.5 * (2 ** attempt))
raise RuntimeError("post failed after 3 attempts")
Patternটি হলো: আগে ID generate করুন, transport failure-এ retry করুন, logical rejection-এ কখনও না। Server-side dedup প্রতি-API-key ভিত্তিক, প্রায় 5 minutes পর্যন্ত থাকে।
Real production incident: আমরা কীভাবে ours fix করেছি
আমাদের own production diary থেকে, মে 2025। 60-minute window-এ trader bot 22টি buy order দিয়েছিল, সবগুলো matched হয়েছিল, কিন্তু মাত্র 14টি GTC sell accepted হয়েছিল। আটটি position-এর কোনো exit post হয়নি।
Root cause: buy match-এর 800ms-এর মধ্যেই bot GTC sell post করেছিল, chain ERC-1155 transfer confirm করার অনেক আগেই। CLOB "balance: 0" message দিয়ে reject করেছিল; bot error log করেছিল, কিন্তু retry করেনি। আটটি position silently resolution পর্যন্ত ধরে ছিল, take-profit protection ছাড়াই। তিনটি out of the money-তে close হয়েছিল; একটি luck-এর কারণে 0.99-এ close হয়েছিল।
Fix হিসেবে একই token-এর ওপর যেকোনো buy fill আর GTC post-এর মাঝখানে 5-second blocking wait যোগ করা হয়। 30টি paper trade plus 30টি live trade দিয়ে verify করা হয়েছে; তারপর থেকে zero balance-zero errors।
Lesson: loud error-এর চেয়ে silent error path বেশি expensive। এরপর আমরা সব phantom-fill error-এ Telegram alert trigger করেছি, যাতে ভবিষ্যতের drift mode seconds-এর মধ্যে visible হয়।
Code: detect-then-act post-order pattern
Production buy-then-post pattern.
def buy_then_post_tp(token_id, size, buy_price, tp_price):
# 1. Place FOK buy
buy_args = OrderArgs(token_id=token_id, price=buy_price, size=size, side="BUY")
buy_resp = c.create_and_post_order(buy_args, OrderType.FOK)
if buy_resp.status != "matched":
return {"ok": False, "stage": "buy", "reason": buy_resp.status}
# 2. Wait for on-chain settlement (5s sane default; bump for congestion)
if not wait_for_settlement(token_id, expected_size=size, timeout=15):
return {"ok": False, "stage": "settle", "reason": "timeout"}
# 3. Confirm minimum size for GTC
if size < 5:
# GTC won't accept; fall back to ride-to-resolve or FOK sell at TP later
return {"ok": True, "stage": "buy_only", "note": "size<5, no GTC posted"}
# 4. Post GTC sell
sell_args = OrderArgs(token_id=token_id, price=tp_price, size=size, side="SELL")
sell_resp = c.create_and_post_order(sell_args, OrderType.GTC)
return {"ok": sell_resp.status in ("posted","live"), "buy": buy_resp, "sell": sell_resp}
Patternটি common failure modes থেকে বাঁচে: phantom-fill, transient network drop, under-minimum GTC size। Strategy layer-কে যথেষ্ট information দেয়, যাতে retry, log, বা alert - কোনটা করতে হবে তা ঠিক করা যায়।














