Polymarket Bot Tutorial · Sura ya 12 kati ya 32
Jinsi ya kudetect Polymarket phantom fills (orders zinazoonekana zimefilled lakini hazijafilled), kuimplement idempotent retries, kutofautisha status=matched na rested-on-book, na kuishi transient failures.
Sura hii inafunika nini
Phantom fill ni Polymarket-specific failure mode ambapo CLOB inakubali match lakini chain bado haijaconfirm ERC-1155 transfer. Follow-up order ndani ya ~sekunde 5 inarejected na misleading "balance: 0" error. Tiba ni idempotence na settlement wait. Sura hii ni production playbook tuliyolipa kwa pesa halisi.
- Ni nini phantom fill
- status=matched vs status=delayed vs status=posted
- Polling pattern: poll status kabla ya kuadhimisha
- FOK kama anti-phantom-fill
- Idempotent retries na client_order_id
- Real production incident: jinsi tulivyofix yetu
- Code: detect-then-act post-order pattern
Ni nini phantom fill
Phantom fill ni wakati CLOB API inajibu order yako na status: "matched" lakini on-chain ERC-1155 transfer bado haijasettle. CLOB matcher ni haraka kuliko Polygon block production (~sekunde 2 per block). Kwa takriban sekunde 2-5 baada ya API match, wallet yako on-chain haishikilii tokens ambazo matcher inasema unamiliki.
Bot bug inaemerge wakati follow-up action - kawaida GTC sell ya kuipost take-profit - inarun ndani ya window hiyo. CLOB inacheck chain balance, inaona zero, na inarejects na not enough balance / allowance: balance: 0, order amount: N. Error message inalaumu allowance; sababu ni settlement lag.
Mara ya kwanza hii inatokea unaaasume allowance bug na unapoteza saa. Tiba ni rahisi: subiri, hakiki, kisha post.
status=matched vs status=delayed vs status=posted
Order placement response inajumuisha status field na values tatu zinazojali.
matched: order ilimatch dhidi ya book mara moja. Inventory itasettle katika sekunde 2-5. Hii ndio FOK/FAK zinarudisha zinaposhinda.delayed: matcher haikuweza kusettle synchronously na ilipangia match. Nadra; kawaida inaonyesha congestion. Treat kamamatchedkwa purpose ya wait + verify pattern.posted(pia inaitwalive): order inapumzika kwenye book bila kufilled. Inarudishwa na GTC orders ambazo hazikumatch mara moja. Inventory haiathiriki; hakuna follow-up action inayohitajika bado.
Decision rule: ikiwa status ni matched au delayed, usiweke follow-up yoyote inayohitaji new inventory hadi uhakiki chain transfer.
Polling pattern: poll status kabla ya kuadhimisha
Verification pattern: baada ya successful match, poll CTF balance hadi ireflect new tokens, kisha endelea.
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: sekunde 2-5 katika good network conditions, hadi 15s wakati wa Polygon congestion. 5-second wait inafunika 95% ya cases; kwa production weka timeout kwa 15s na alert kwenye timeout.
Kwa high-frequency bots ambazo haziwezi kublock, alternative ni event-subscription: angalia CTF TransferSingle event kwa proxy address yako na trigger downstream actions kwenye receipt. Hii inapush wait kwa queue badala ya kublock strategy loop.
FOK kama anti-phantom-fill
Kuchagua FOK juu ya FAK ni partial defense dhidi ya phantom-fill chaos. FOK ofuli fills entire order au inarudisha cancelled; FAK inaweza kurudisha filled_size ambayo ni partial. Wakati partial fill inafollowed na GTC sell sized kwa original order, sell inashindwa kwenye settlement-lag pamoja na size mismatch - bugs mbili zinacompound.
Na FOK, size ni binary: ofuli full size ilimatch au hakuna kitu kilichofanya. Follow-up posting logic daima inajua nini cha kutarajia.
Hii haifuti haja ya wait - hata perfect FOK match iko subject kwa 2-5 second settlement window. Lakini inaondoa class moja ya bookkeeping divergence.
Idempotent retries na client_order_id
Network failures wakati wa order placement zinaunda worst-case scenario: HTTP call ya bot ilitimeout, lakini order inaweza kuwa au isiwe ilipokelewa. Kuretry naively kunaweza kudouble-place; kutoretry kunaweza kuangusha position.
Fix ni client_order_id field kwenye order placement. Tengeneza deterministic UUID per intended order; ikiwa server imeona ID hiyo kabla, inarudisha existing order status badala ya kutengeneza duplicate.
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: tengeneza ID kwanza, retry kwenye transport failure, kamwe kwenye logical rejection. Server-side dedup ni per-API-key, inadumu ~dakika 5.
Real production incident: jinsi tulivyofix yetu
Kutoka production diary yetu wenyewe, Mei 2025. Window ya dakika 60 ambapo trader bot ilipost FOK buys 22, zote zilimatch, lakini GTC sells 14 tu zilikubaliwa. Positions 8 hazikuwa na exit iliyopost.
Root cause: bot ilipost GTC sell ndani ya 800ms ya buy match, vizuri kabla chain ikahakiki ERC-1155 transfer. CLOB ilirejects na "balance: 0" message; bot iliilogi error lakini haikuretry. Positions 8 silently zikariden hadi resolution bila take-profit protection. Tatu zilifunga out of the money; moja ilifunga 0.99 kwa luck.
Fix iliship kama blocking wait ya sekunde 5 kati ya buy fill yoyote na GTC post yoyote kwa token sawa. Imeverified kupitia trades 30 za paper pamoja na trades 30 za live; zero balance-zero errors tangu hapo.
Somo: silent error path ni gharama zaidi kuliko loud one. Baada ya hii tulifanya phantom-fill errors zote zi-trigger Telegram alert, ili future drift mode ingeonekana ndani ya sekunde.
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 inaishi common failure modes: phantom-fill, transient network drop, under-minimum GTC size. Inarudisha information ya kutosha kwa strategy layer kuamua nini cha kuretry vs log vs alert.














