Polymarket Bot Tutorial · अध्याय 12 of 32

Polymarket phantom fills (ऐसे ऑर्डर जो filled दिखते हैं लेकिन वास्तव में नहीं होते), idempotent retries लागू करना, status=matched को rested-on-book से अलग पहचानना, और transient failures से बचकर निकलना कैसे है।

यह अध्याय क्या कवर करता है

एक phantom fill Polymarket-विशिष्ट failure mode है, जहाँ CLOB मैच को स्वीकार कर लेता है लेकिन chain ने अभी तक ERC-1155 transfer को confirm नहीं किया होता। ~5 seconds के भीतर आने वाला follow-up order भ्रामक "balance: 0" error के साथ reject हो जाता है। इसका इलाज है idempotence और settlement wait। यह अध्याय वही production playbook है जिसके लिए हमने असली पैसे चुकाए थे।

  • phantom fill क्या है
  • status=matched vs status=delayed vs status=posted
  • Polling pattern: celebrate करने से पहले status poll करें
  • phantom-fill के खिलाफ FOK
  • 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 को hold नहीं कर रहा होता, जिन्हें matcher आपके होने का दावा करता है।

Bot bug तब उभरता है जब कोई follow-up action - आम तौर पर take-profit पोस्ट करने के लिए GTC sell - उस window के भीतर चलती है। CLOB chain balance चेक करता है, zero देखता है, और not enough balance / allowance: balance: 0, order amount: N के साथ reject कर देता है। Error message allowance को blame करता है; असली कारण settlement lag है।

पहली बार ऐसा होने पर आप इसे allowance bug समझते हैं और एक घंटा बर्बाद कर देते हैं। इलाज सरल है: wait करें, verify करें, फिर post करें।

status=matched vs status=delayed vs status=posted

Order placement response में एक status field होती है, जिसमें तीन values मायने रखती हैं।

  • matched: order तुरंत book के साथ match हो गया। Inventory 2-5 seconds में settle होगी। सफल होने पर FOK/FAK यही 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 को chain transfer verify किए बिना place न करें।

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% मामलों को cover करता है; production के लिए timeout 15s रखें और timeout पर alert करें।

ऐसे high-frequency bots के लिए जो block नहीं हो सकते, एक alternative है event-subscription: अपने proxy address के लिए CTF के TransferSingle event को watch करें और receipt मिलते ही downstream actions trigger करें। इससे wait strategy loop को block करने के बजाय queue में चला जाता है।

phantom-fill के खिलाफ FOK

FAK की बजाय FOK चुनना phantom-fill chaos के खिलाफ आंशिक defense है। FOK या तो पूरे order को fill करता है या cancelled return करता है; FAK filled_size के रूप में partial return दे सकता है। जब partial fill के बाद original order के size के हिसाब से GTC sell किया जाता है, तो sell settlement-lag और size mismatch - दो कंपाउंड होने वाले bugs - के कारण fail हो जाता है।

FOK के साथ size binary होता है: या तो पूरा 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 failures worst-case scenario बनाते हैं: bot का HTTP call timeout हो गया, लेकिन order मिला था या नहीं, यह पता नहीं। Blind retry double-place कर सकता है; retry न करने से position छूट सकती है।

इसका fix है 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: हमने अपना कैसे fix किया

हमारी अपनी production diary से, मई 2025। 60-minute window में trader bot ने 22 buy orders place किए, सभी matched हुए, लेकिन सिर्फ 14 GTC sells accepted हुए। आठ positions के लिए exit पोस्ट नहीं हुआ था।

Root cause: bot ने buy match के 800ms के भीतर GTC sell post कर दिया, यानी chain द्वारा ERC-1155 transfer confirm होने से काफी पहले। CLOB ने "balance: 0" message के साथ reject किया; bot ने error log किया लेकिन retry नहीं किया। आठ positions silently resolution तक चली गईं, बिना take-profit protection के। तीन out of the money close हुए; एक luck से 0.99 पर close हुआ।

Fix एक 5-second blocking wait के रूप में shipped हुआ, जो एक ही token पर किसी भी buy fill और किसी भी GTC post के बीच लगाया गया। 30 paper trades और 30 live trades से verify किया गया; तब से zero balance-zero errors।

Lesson: एक silent error path, loud one से ज़्यादा महंगा होता है। इसके बाद हमने सभी phantom-fill errors को Telegram alert trigger करने के लिए set किया, ताकि future 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 से बचता है: phantom-fill, transient network drop, under-minimum GTC size। यह strategy layer को इतना information देता है कि वह तय कर सके क्या retry करना है, क्या log करना है, और क्या alert करना है।

अक्सर पूछे जाने वाले प्रश्न

Polymarket पर phantom fill क्या होता है?
Phantom fill तब होता है जब आपका bot मान लेता है कि order fill हो गया, लेकिन exchange उसे अभी तक filled नहीं मानता (या partially filled मानता है)। यह तब होता है जब client code HTTP 200 response को confirmation समझ लेता है, जबकि response सिर्फ यह बताता है कि order matching engine में accept हो गया। हमने यह कठोर तरीके से सीखा - हमारे trader history में commits 06deaef, 8bb7761, और e68a087 ठीक इसी को fix करते हैं।
Phantom fills से कैसे बचें?
तीन नियम: (1) buys के लिए FOK orders use करें - order या तो पूरी तरह fill होगा या पूरी तरह जाएगा, कभी ambiguous नहीं। (2) किसी भी non-matched status को unfilled मानें - order status को तब तक poll करें जब तक status=matched OR amount_filled > 0 न हो जाए। (3) idempotence के लिए client_order_id (V2 में clientOrderId) use करें, ताकि 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 मानना चाहिए।
अगर मेरा bot order भेजने और response देखने के बीच crash हो जाए तो?
Restart पर, SDK से open orders और recent fills query करें। अपने local diary/state से reconcile करें - अगर आपने समय T पर order भेजा था लेकिन कोई record नहीं है, तो T के बाद के orders query करें और client_order_id से match करें। अगर फिर भी missing है, तो order matching engine तक पहुँचा ही नहीं और आप safely re-send कर सकते हैं।