Polymarket Bot Tutorial · Kabanata 12 ng 32
Paano i-detect ang Polymarket phantom fills (orders na mukhang filled pero hindi), mag-implement ng idempotent retries, paghiwalayin ang status=matched mula sa rested-on-book, at malampasan ang transient failures.
Ano ang sinasaklaw ng kabanatang ito
Ang phantom fill ay Polymarket-specific failure mode kung saan kinikilala ng CLOB ang isang match pero hindi pa kinukumpirma ng chain ang ERC-1155 transfer. Ang follow-up order sa loob ng ~5 segundo ay tinatanggihan na may misleading na "balance: 0" error. Ang lunas ay idempotence at isang settlement wait. Ang kabanatang ito ay ang production playbook na binayaran namin nang totoong pera.
- Ano ang phantom fill
- status=matched vs status=delayed vs status=posted
- Polling pattern: i-poll ang status bago magdiwang
- FOK bilang anti-phantom-fill
- Idempotent retries gamit ang client_order_id
- Tunay na production incident: paano namin inayos ang sa amin
- Code: detect-then-act post-order pattern
Ano ang phantom fill
Ang phantom fill ay kapag tumutugon ang CLOB API sa iyong order na may status: "matched" ngunit hindi pa nag-settle ang on-chain ERC-1155 transfer. Ang CLOB matcher ay mas mabilis kaysa sa Polygon block production (~2s bawat block). Sa loob ng halos 2-5 segundo pagkatapos ng API match, ang iyong wallet ay hindi on-chain humahawak sa mga tokens na sinasabi ng matcher na pag-aari mo.
Ang bug ng bot ay lumalabas kapag ang follow-up action - karaniwang GTC sell para mag-post ng take-profit - ay tumatakbo sa loob ng window na iyon. Sine-check ng CLOB ang chain balance, nakikita ang zero, at tinatanggihan na may not enough balance / allowance: balance: 0, order amount: N. Ang error message ay sinisisi ang allowance; ang sanhi ay settlement lag.
Sa unang pagkakataon na nangyari ito, ipinagpapalagay mo ang isang allowance bug at sinasayang ang isang oras. Ang lunas ay simple: maghintay, i-verify, pagkatapos i-post.
status=matched vs status=delayed vs status=posted
Ang order placement response ay may kasamang status field na may tatlong values na mahalaga.
matched: ang order ay tumugma sa book kaagad. Ang inventory ay mag-settle sa 2-5 segundo. Ito ang ibinabalik ng FOK/FAK kapag nagtagumpay.delayed: hindi maaaring mag-settle ng matcher nang synchronous at na-queue ang match. Bihira; karaniwang nagpapahiwatig ng congestion. Tratuhin tulad ngmatchedpara sa layunin ng wait + verify pattern.posted(tinatawag dinglive): ang order ay nagpapahinga sa book na hindi pa filled. Ibinabalik ng GTC orders na hindi agad nag-match. Ang inventory ay hindi naaapektuhan; walang kailangang follow-up action sa ngayon.
Ang decision rule: kung ang status ay matched o delayed, huwag maglagay ng anumang follow-up na nangangailangan ng bagong inventory hanggang sa ma-verify mo ang chain transfer.
Polling pattern: i-poll ang status bago magdiwang
Ang verification pattern: pagkatapos ng matagumpay na match, i-poll ang CTF balance hanggang sa makita nito ang mga bagong tokens, pagkatapos magpatuloy.
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
Karaniwang settlement: 2-5 segundo sa magandang network conditions, hanggang 15s sa panahon ng Polygon congestion. Ang 5-segundong paghihintay ay sumasaklaw sa 95% ng mga kaso; para sa production itakda ang timeout sa 15s at mag-alerto sa timeout.
Para sa high-frequency bots na hindi kayang mag-block, ang alternatibo ay event-subscription: panoorin ang TransferSingle event ng CTF para sa iyong proxy address at i-trigger ang downstream actions sa pagtanggap. Itinutulak nito ang paghihintay sa queue sa halip na harangan ang strategy loop.
FOK bilang anti-phantom-fill
Ang pagpili ng FOK kaysa FAK ay partial defense laban sa phantom-fill chaos. Ang FOK ay alinman fully fill ang buong order o ibabalik ang cancelled; ang FAK ay maaaring magbalik ng filled_size na partial. Kapag ang partial fill ay sinundan ng GTC sell na sukat sa orihinal na order, ang sell ay nabigo sa settlement-lag plus size mismatch - dalawang compounding bugs.
Sa FOK, ang sukat ay binary: alinman buong sukat ang tumugma o wala. Ang follow-up posting logic ay palaging alam kung ano ang aasahan.
Hindi nito inaalis ang pangangailangan ng paghihintay - kahit ang perpektong FOK match ay napapailalim sa 2-5 segundong settlement window. Ngunit tinatanggal nito ang isang klase ng bookkeeping divergence.
Idempotent retries gamit ang client_order_id
Ang network failures sa panahon ng order placement ay lumilikha ng worst-case scenario: nag-timeout ang HTTP call ng bot, ngunit ang order ay maaaring natanggap o hindi. Ang naive na pag-retry ay maaaring dumoble sa pag-place; ang hindi pag-retry ay maaaring mag-drop ng position.
Ang fix ay ang client_order_id field sa order placement. Bumuo ng deterministic UUID bawat intended order; kung ang server ay nakakita na sa ID na iyon, ibabalik nito ang status ng umiiral na order sa halip na lumikha ng 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")
Ang pattern: bumuo ng ID muna, mag-retry sa transport failure, hindi kailanman sa logical rejection. Ang server-side dedup ay per-API-key, tumatagal ng ~5 minuto.
Tunay na production incident: paano namin inayos ang sa amin
Mula sa aming sariling production diary, Mayo 2025. Isang 60-minutong window kung saan naglagay ang trader bot ng 22 buy orders, lahat ay nag-match, pero 14 GTC sells lamang ang tinanggap. Walong positions ang walang na-post na exit.
Root cause: nag-post ang bot ng GTC sell sa loob ng 800ms ng buy match, bago pa kinumpirma ng chain ang ERC-1155 transfer. Tinanggihan ng CLOB sa pamamagitan ng "balance: 0" message; nag-log ang bot ng error pero hindi nag-retry. Walong positions ang tahimik na sumakay hanggang sa resolution na walang take-profit protection. Tatlo ang nagsara out of the money; isa ang nagsara sa 0.99 sa pamamagitan ng suwerte.
Ang fix ay ipinakalat bilang 5-segundong blocking wait sa pagitan ng anumang buy fill at anumang GTC post sa parehong token. Na-verify sa pamamagitan ng 30 paper trades plus 30 live trades; zero balance-zero errors mula noon.
Ang aral: ang silent error path ay mas mahal kaysa sa malakas. Pagkatapos nito ginawa naming mag-trigger ng Telegram alert ang lahat ng phantom-fill errors, kaya ang future drift mode ay makikita sa loob ng ilang segundo.
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}
Ang pattern ay nakakaligtas sa karaniwang failure modes: phantom-fill, transient network drop, under-minimum GTC size. Nagbabalik ng sapat na impormasyon para sa strategy layer na magdesisyon kung ano ang i-retry vs. i-log vs. i-alert.














