FOK vs GTC Execution Semantics
Fill-or-Kill attempts first for maker fee savings, Good-Til-Canceled is the fallback. The HE-06 bug: status=live means pending on the book, not filled. How to distinguish resting orders from actual fills.
The HE-06 bug looked like a ghost trade. Hermes logged "order placed" and moved on to the next signal. The wallet balance showed a small debit. But the position never appeared in the resolver, and the PnL never materialized.
The root cause was a single line of logic: Hermes was treating status=live as a successful fill.
The Semantics
Polymarket's order creation endpoint returns a JSON blob with a status field. That field has several possible values, and only one of them means the order actually traded:
| Status | Meaning |
|---|---|
live | Order is resting on the book. No fill yet. Waiting for counterparty. |
matched | Order has been matched against another order. Settlement is in progress. |
filled | Order is fully settled. Position exists. |
partial | Partially filled. Remainder is still live on the book. |
canceled | Order was canceled before any fill. |
expired | Order timed out. No fill. |
status=live is the default state after posting a new GTC order. It is a healthy, normal response — but it is not a confirmation of trade. Treating it as one was the HE-06 bug.
FOK vs GTC
Polymarket supports both Fill-or-Kill (FOK) and Good-Til-Canceled (GTC) semantics via the order_type field on order creation.
- FOK: Attempt to fill the entire order immediately at the specified price. If the book cannot absorb the full size at that price, the order is killed. Either you get a full fill right now, or you get nothing. No resting.
- GTC: Post the order to the book and let it sit. Matches happen when a counterparty crosses the spread. The order stays live until filled, canceled, or expired.
Hermes's execution strategy is FOK-first:
- Query the orderbook depth. Compute the walked price at intended size.
- If walked price is acceptable: attempt FOK at the aggressive price. If it fills, done.
- If FOK fails (insufficient depth): fall back to GTC at a more conservative maker price. Let the order rest.
- Poll the order status until it transitions out of
live.
The rationale for FOK-first is fee optimization — a successful immediate fill can save basis points that compound across hundreds of trades. The rationale for GTC fallback is execution reliability — thin books will reject a lot of FOK attempts and you need a path that eventually trades.
Inline Diagram — Decision Tree
The HE-06 Fix
The bug fix was a state machine:
# Buggy
resp = clob.place_order(order)
if resp.get("status") == "live": # WRONG — this is pending, not filled
mark_position_as_filled(signal_id)
# Correct
resp = clob.place_order(order)
order_id = resp["id"]
for attempt in range(30):
status = clob.get_order(order_id)
if status["status"] in ("matched", "filled"):
mark_position_as_filled(signal_id, fill_price=status["avg_price"])
break
if status["status"] in ("canceled", "expired"):
mark_signal_as_unexecuted(signal_id)
break
time.sleep(2)
else:
# Still live after 60s — cancel and retry with GTC maker order
clob.cancel_order(order_id)
place_gtc_maker(signal_id)
The Rule
Order creation does not equal order execution. FOK attempts first, GTC falls back, polling confirms the fill. Treat every status=live response as "not yet" and build the state machine that resolves it.