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 کرنا ہے۔

اکثر پوچھے جانے والے سوالات

Polymarket پر phantom fill کیا ہے؟
Phantom fill وہ صورت ہے جب آپ کا bot سمجھتا ہے کہ order fill ہو گیا ہے، مگر exchange اسے ابھی تک filled یا partially filled record کرتی ہے۔ یہ تب ہوتا ہے جب client code HTTP 200 response کو confirmation سمجھ لیتا ہے، جبکہ response صرف اتنا بتاتا ہے کہ order matching engine میں accept ہو گیا۔ ہم نے یہ سخت طریقے سے سیکھا - ہمارے trader history کے commits 06deaef، 8bb7761، اور e68a087 اسی مسئلے کو fix کرتے ہیں۔
Phantom fills سے کیسے بچیں؟
تین اصول: (1) buys کے لیے FOK orders استعمال کریں - order یا تو fully filled ہوگا یا مکمل طور پر ختم، کبھی ambiguous نہیں۔ (2) کسی بھی non-matched status کو unfilled سمجھیں - order status کو status=matched یا amount_filled > 0 تک poll کریں۔ (3) idempotence کے لیے client_order_id (V2 میں clientOrderId) استعمال کریں تاکہ retries double-fill نہ کریں۔
status=delayed کا کیا مطلب ہے؟
Order matching engine میں ہے مگر ابھی fully matched نہیں ہوا۔ یہ چند seconds میں match ہو سکتا ہے یا resting ہو سکتا ہے۔ ہمیشہ poll کریں - اگر status 5-10 seconds سے زیادہ delayed رہے اور amount_filled 0 ہو، تو اسے unfilled سمجھیں اور cancel کرنے پر غور کریں۔
Double-filling کے بغیر safely retry کیسے کروں؟
ہر logical trade attempt کے لیے ایک unique client_order_id generate کریں اور ہر retry میں وہی pass کریں۔ Exchange client_order_id کے ذریعے dedupe کرتی ہے، اس لیے same id کے ساتھ retried order کو duplicate سمجھ کر reject کیا جاتا ہے، دوبارہ place نہیں کیا جاتا۔ Implementations: Python OrderArgs.client_order_id، Node CreateOrderOptions.clientOrderId۔
کیا order endpoint سے 200 OK response پر بھروسا کیا جا سکتا ہے؟
نہیں - 200 OK صرف یہ معنی دیتا ہے کہ "آپ کی request matching engine نے accept کر لی"، یہ نہیں کہ "آپ کا order fill ہو گیا"۔ Submission کے بعد orderId کے ذریعے order status poll کرنا ضروری ہے، اور صرف status=matched (یا amount_filled > 0) کو real fill سمجھیں۔
اگر order بھیجنے اور response دیکھنے کے درمیان میرا bot crash ہو جائے تو؟
Restart پر SDK کے ذریعے open orders اور recent fills query کریں۔ اپنے local diary/state کے ساتھ reconcile کریں - اگر آپ نے time T پر order بھیجا تھا مگر کوئی record موجود نہیں، تو T کے بعد کے orders query کریں اور client_order_id کے ذریعے match کریں۔ اگر پھر بھی missing ہو، تو order کبھی matching engine تک نہیں پہنچا تھا اور آپ safely دوبارہ send کر سکتے ہیں۔