Polymarket Bot Tutorial · Chapter 12 of 32
Polymarket phantom fills کو کیسے detect کریں (ایسے orders جو filled لگتے ہیں مگر اصل میں نہیں ہوتے)، idempotent retries implement کریں، status=matched اور rested-on-book میں فرق کریں، اور transient failures سے کیسے بچیں۔
اس chapter میں کیا cover کیا گیا ہے
Phantom fill Polymarket-specific failure mode ہے جس میں CLOB match کو acknowledge کر دیتا ہے مگر chain نے ابھی ERC-1155 transfer کو confirm نہیں کیا ہوتا۔ تقریباً 5 seconds کے اندر follow-up order reject ہو جاتا ہے، اور misleading "balance: 0" error آتا ہے۔ اس کا حل idempotence اور settlement wait ہے۔ یہ chapter production playbook ہے جس کی قیمت ہم نے real money سے ادا کی ہے۔
- Phantom fill کیا ہے
- status=matched بمقابلہ status=delayed بمقابلہ status=posted
- Polling pattern: celebrate کرنے سے پہلے status poll کریں
- FOK بطور anti-phantom-fill
- client_order_id کے ساتھ idempotent retries
- Real production incident: ہم نے اپنا مسئلہ کیسے 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 - عموماً profit لینے کے لیے GTC sell - اسی window کے اندر run ہو جائے۔ CLOB chain balance check کرتا ہے، zero دیکھتا ہے، اور not enough balance / allowance: balance: 0, order amount: N کے ساتھ reject کر دیتا ہے۔ Error message الزام allowance پر لگاتا ہے؛ اصل cause settlement lag ہوتا ہے۔
پہلی بار یہ ہونے پر لگتا ہے کہ allowance bug ہے، اور ایک گھنٹہ ضائع ہو جاتا ہے۔ Cure سادہ ہے: wait کریں، verify کریں، پھر post کریں۔
status=matched بمقابلہ status=delayed بمقابلہ status=posted
Order placement response میں ایک status field ہوتی ہے جس کی تین values اہم ہیں۔
matched: order فوراً book کے ساتھ match ہو گیا۔ Inventory 2-5 seconds میں settle ہو جائے گی۔ FOK/FAK successful ہونے پر یہی return کرتے ہیں۔delayed: matcher synchronous طور پر settle نہیں کر سکا اور match کو queue کر دیا گیا۔ یہ rare ہے؛ عموماً congestion کی نشاندہی کرتا ہے۔ wait + verify pattern کے لیے اسےmatchedکی طرح treat کریں۔posted(جسےliveبھی کہا جاتا ہے): order book پر resting ہے اور fill نہیں ہوا۔ یہ GTC orders سے return ہوتا ہے جو فوراً match نہیں ہوئے۔ Inventory متاثر نہیں ہوتی؛ ابھی follow-up action کی ضرورت نہیں۔
Decision rule: اگر status matched یا delayed ہو، تو نئی inventory کی ضرورت والے کسی بھی follow-up کو تب تک place نہ کریں جب تک chain transfer verify نہ ہو جائے۔
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
Typical settlement: اچھے network conditions میں 2-5 seconds، اور Polygon congestion کے دوران 15s تک۔ 5-second wait 95% cases کو cover کرتی ہے؛ production کے لیے timeout 15s رکھیں اور timeout پر alert کریں۔
High-frequency bots کے لیے جو block کرنا afford نہیں کر سکتے، ایک alternative event-subscription ہے: CTF کے TransferSingle event کو اپنے proxy address کے لیے watch کریں اور receipt پر downstream actions trigger کریں۔ اس سے wait strategy loop کو block کرنے کے بجائے queue میں shift ہو جاتی ہے۔
FOK بطور anti-phantom-fill
FAK کے مقابلے میں FOK کو choose کرنا phantom-fill chaos کے خلاف ایک partial defense ہے۔ FOK یا تو پوری order fill کرتا ہے یا cancelled return کرتا ہے؛ FAK filled_size کو partial return کر سکتا ہے۔ جب partial fill کے بعد original order size کے مطابق GTC sell post کی جائے، تو sell settlement-lag plus size mismatch کی وجہ سے fail ہو جاتی ہے - دو compounding bugs۔
FOK کے ساتھ size binary ہوتی ہے: یا تو پوری size match ہوئی یا کچھ نہیں ہوا۔ Follow-up posting logic ہمیشہ جانتی ہے کہ کیا expect کرنا ہے۔
یہ wait کی ضرورت ختم نہیں کرتا - حتیٰ کہ perfect FOK match بھی 2-5 second settlement window کے تابع ہوتا ہے۔ مگر یہ bookkeeping divergence کی ایک کلاس ختم کر دیتا ہے۔
client_order_id کے ساتھ idempotent retries
Order placement کے دوران network failures worst-case scenario پیدا کرتی ہیں: bot کی HTTP call timeout ہو گئی، مگر order receive ہوا یا نہیں، معلوم نہیں۔ Blind retry سے double-place ہو سکتا ہے؛ retry نہ کرنے سے position miss ہو سکتی ہے۔
اس کا حل order placement میں client_order_id field ہے۔ ہر intended order کے لیے deterministic UUID generate کریں؛ اگر server نے وہ ID پہلے دیکھی ہو، تو وہ duplicate بنانے کے بجائے existing order کی status واپس کرتا ہے۔
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: ہم نے اپنا مسئلہ کیسے fix کیا
ہمارے اپنے production diary سے، May 2025۔ 60-minute window میں trader bot نے 22 buy orders place کیے، سب match ہوئے، مگر صرف 14 GTC sells accept ہوئیں۔ آٹھ positions کے لیے کوئی exit post نہیں ہوا تھا۔
Root cause: bot نے buy match کے 800ms کے اندر GTC sell post کر دی، یعنی chain کے ERC-1155 transfer confirm کرنے سے بہت پہلے۔ CLOB نے "balance: 0" message کے ساتھ reject کیا؛ bot نے error log تو کیا مگر retry نہیں کیا۔ آٹھ positions خاموشی سے 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 trades اور 30 live trades سے verify کیا گیا؛ اس کے بعد سے zero balance-zero errors آئے ہیں۔
سبق یہ ہے: خاموش error path، loud error path سے زیادہ مہنگا ہوتا ہے۔ اس کے بعد ہم نے phantom-fill errors کو Telegram alert trigger کر دیا، تاکہ مستقبل میں کوئی drift mode seconds میں نظر آ جائے۔
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 عام failure modes کے باوجود survive کرتا ہے: phantom-fill، transient network drop، under-minimum GTC size۔ یہ strategy layer کو اتنی information دیتا ہے کہ وہ decide کر سکے کیا retry کرنا ہے، کیا log کرنا ہے، اور کیا alert کرنا ہے۔














