One of the harder parts of an automated trading system is not submitting an order.
It is knowing what actually happened after the order was submitted.
A bot can receive an incomplete response.
An order can remain unresolved.
A fill can arrive later.
A connection can disappear between two events.
The application can restart before it finishes processing the result.
That creates a dangerous situation if the bot treats every order response as final.
For a Polymarket trading bot, I prefer to model order execution as an explicit state machine rather than a single boolean such as:
order = successful
A simplified internal lifecycle looks more like:
INTENDED
↓
SUBMITTED
↓
ACCEPTED
↓
MATCHED
↓
FILLED
with other paths such as:
REJECTED
CANCELLED
FAILED
RETRYING
UNKNOWN
INCONSISTENT
The exact state model can vary by implementation.
The important part is that unknown should be a real state.
Why a single order status is not enough
A simple trading bot often does something like:
submit order
↓
get response
↓
if success:
mark filled
That works only when every step behaves exactly as expected.
A more realistic execution flow looks like:
Strategy
↓
Order intent
↓
Submission
↓
Exchange response
↓
Matching
↓
Fill
↓
Transaction
↓
Confirmation
↓
Position
There are several different facts here.
The strategy intended to place an order.
The system submitted something.
The venue accepted or rejected it.
The order may or may not have matched.
A fill may or may not have occurred.
The resulting execution may still need to be tracked further.
Collapsing all of that into one status makes recovery much harder.
An internal order state machine
I usually find it easier to reason about execution with explicit states.
For example:
INTENDED
↓
SUBMITTED
↓
ACCEPTED
↓
MATCHED
↓
FILLED
Possible failure paths:
SUBMITTED → REJECTED
SUBMITTED → FAILED
ACCEPTED → CANCELLED
MATCHED → PARTIAL
And when the system cannot establish what happened:
SUBMITTED
↓
UNKNOWN
The state machine is not meant to describe every possible platform implementation.
It is an internal representation that lets the rest of the trading system make decisions based on what is actually known.
Pending does not mean failed
Suppose the strategy submits an order and does not immediately receive a final outcome.
The wrong response is often:
No final response
↓
Assume failed
↓
Submit again
That can create duplicate exposure.
The order may still be alive.
It may have been accepted but the application has not observed the next event yet.
It may already have matched.
The system simply does not know yet.
So I prefer:
SUBMITTED
↓
PENDING
↓
WAIT / VERIFY
rather than immediately converting uncertainty into failure.
The dangerous UNKNOWN state
This is the state I think deserves the most attention.
Imagine:
Order submitted
↓
Network interruption
↓
Response lost
The bot now has:
Local state:
UNKNOWN
That does not mean:
Order failed
It also does not mean:
Order succeeded
It means:
The application does not currently know.
That distinction matters.
If the bot immediately retries, the first order could still exist.
You could end up with:
Original order
+
Retry order
=
More exposure than intended
Unknown state therefore needs its own recovery path.
Unknown should block unsafe assumptions
A useful rule is:
Don't convert uncertainty into a successful or failed state just because the strategy needs an answer.
Instead:
UNKNOWN
↓
PAUSED / NO RETRY
↓
RECONCILE
↓
VERIFY
Only after the system establishes enough information should it decide whether the order should be considered complete, failed, cancelled, or retried.
This is one reason execution verification and reconciliation belong close to the trading system rather than being treated as dashboard features.
Retrying an order is not as simple as submitting it again
A common implementation is:
if order failed:
retry()
The difficult part is determining whether the order actually failed.
Consider:
Order 123
↓
Submitted
↓
Response timeout
The application might classify that as:
FAILED
But the real state could be:
ACCEPTED
or:
MATCHED
or:
FILLED
The network failure tells you that the application didn't get a response.
It does not necessarily tell you what happened to the order.
So the retry decision should depend on verified state, not just the absence of a response.
A safer flow is:
Request timeout
↓
Mark UNKNOWN
↓
Query / reconcile
↓
Did the order exist?
↓
What is its current state?
↓
Only then decide:
retry / cancel / continue
Partial fills make the state model more important
Suppose the strategy intends:
100
The actual execution is:
40 filled
60 remaining
That is neither a clean failure nor a clean completion.
The system needs to represent the difference between:
requested quantity = 100
filled quantity = 40
remaining quantity = 60
The resulting state could be represented internally as:
PARTIALLY_FILLED
The important part is that downstream calculations use the actual fill.
For example:
Order
↓
40 Fill
↓
Position +40
↓
Exposure
↓
Risk
not:
Order 100
↓
Assume Position +100
This connects directly to position reconciliation.
A trading system cannot maintain reliable risk controls when it confuses order intent with actual execution.
Order state affects position state
The order state machine should not live in isolation.
It feeds other parts of the system.
A useful dependency chain is:
Order
↓
Fill
↓
Position
↓
Exposure
↓
Risk
For example:
Order = ACCEPTED
does not automatically mean:
Position increased
And:
Order = INTENDED
definitely does not mean:
Position increased
The position should be based on actual execution.
That sounds obvious, but this distinction becomes important when processing real-time events, partial fills, retries, reconnects, and application restarts.
Execution state and transaction state should stay separate
Another useful separation is between the order execution state and what happens afterward.
For example:
ORDER
↓
FILL
↓
TRANSACTION
↓
CONFIRMATION
↓
SETTLEMENT
↓
POSITION
I don't want one field called:
status = filled
to represent every stage of that lifecycle.
A system may know that a trade matched while still waiting on a later stage.
That is why my execution-verification work treats these stages separately.
Repository:
https://github.com/casatrickdev/polymarket-execution-verifier
The verifier is designed around checking execution across order, fill, transaction, and settlement states rather than assuming that one event tells the entire story.
What happens after a restart?
Imagine the application crashes here:
SUBMITTED
↓
?
The process restarts several seconds later.
The local database might contain:
Order ID: 123
State: SUBMITTED
The application should not simply continue from there as though nothing happened.
A safer startup process is:
Load local state
↓
Find unresolved orders
↓
Query / reconcile current state
↓
Update local state
↓
Recalculate position
↓
Recalculate exposure
↓
Check risk
↓
Resume trading
That makes unresolved orders part of startup recovery.
This is closely related to position reconciliation: the system needs to re-establish a trustworthy state before creating additional risk.
WebSocket events should update state, not define reality
Real-time events are useful for keeping local state current.
But I don't want the local state machine to assume:
event received = complete truth forever
Consider:
Order submitted
↓
Fill event
↓
WebSocket disconnect
↓
More execution activity
↓
Reconnect
Some events may have been missed.
The application now needs a way to establish current state again.
So I think about real-time processing and reconciliation as two complementary mechanisms:
Real-time events
↓
Fast local updates
Reconciliation
↓
Recovery and verification
The first keeps the system responsive.
The second gives it a way to recover from uncertainty.
Duplicate events are another problem
The opposite problem can also happen.
Instead of missing an event, the application might process the same logical event more than once.
For example:
Fill event
↓
Position +40
Same event processed again
↓
Position +80
That creates incorrect state even though the connection was never lost.
This is why event handling should be designed with idempotency and deduplication in mind.
The system needs to know which execution events have already been applied.
That state belongs in the execution layer rather than being left to the strategy.
Order state should be observable
A useful trading dashboard should not simply show:
Orders: 14
It should make unresolved conditions visible.
For example:
SUBMITTED 3
ACCEPTED 2
PARTIAL 1
FILLED 14
REJECTED 1
CANCELLED 2
UNKNOWN 1
The exact numbers are only examples.
The point is that the operator should be able to see where execution is getting stuck.
An UNKNOWN order should be much more visible than a successful order that needs no action.
Unknown orders should have a timeout policy
A system can also define how long an order is allowed to remain unresolved.
Conceptually:
UNKNOWN
↓
recheck
↓
still unknown
↓
alert
↓
continue reconciliation
The timeout should not automatically mean:
cancel and retry
because the system still needs to understand whether the original order exists and what exposure it may have created.
A useful policy might distinguish between:
UNKNOWN for 1 second
and:
UNKNOWN for 5 minutes
The longer uncertainty persists, the more aggressively the control plane may need to intervene.
Risk controls should understand execution uncertainty
Risk management often focuses on:
Position limits
Exposure limits
Daily loss
But execution uncertainty can itself become a reason to block trading.
For example:
Open position:
+40
Unresolved order:
+60
Known exposure:
40
Potential exposure:
100
Depending on the strategy, treating the unresolved order as irrelevant could lead to incorrect decisions.
This doesn't necessarily mean counting every unknown order as filled.
It means the system should know that uncertainty exists and apply an appropriate policy.
That is an important distinction.
A control plane can make these decisions explicit
This is where order management connects to the broader trading control plane.
The strategy should not have to understand every operational failure mode.
Instead:
TRADING BOT
│
┌──────────────┼──────────────┐
↓ ↓ ↓
Strategy Execution Risk
│
↓
Order State
│
┌───────────┴───────────┐
↓ ↓
Verification Reconciliation
│ │
└───────────┬───────────┘
↓
CONTROL PLANE
│
Pause / Resume
Alert / Recover
The control plane can decide whether the system is allowed to continue trading.
For example:
UNKNOWN ORDER
↓
Trading blocked
↓
Reconcile
↓
Execution verified
↓
Risk checked
↓
Trading allowed
That keeps operational safety separate from strategy logic.
An example failure flow
Consider this sequence:
1. Strategy decides to buy 100
2. Order is submitted
3. Network response times out
4. Local state becomes UNKNOWN
5. Strategy wants to retry
6. Control Plane blocks retry
7. Execution state is reconciled
8. Original order is found
9. 40 units were filled
10. Position is updated to +40
11. Risk is recalculated
12. Remaining quantity is handled explicitly
Without the state machine, the system might have simply retried the order at step 5.
That is exactly the kind of situation where a small implementation detail can turn into an unnecessary position.
Testing order-state handling
I would test explicit failure scenarios rather than only the happy path.
Order accepted
→ continue
Order rejected
→ record rejection
Order cancelled
→ record cancellation
Partial fill
→ update actual quantity
Response timeout
→ UNKNOWN
WebSocket disconnect
→ preserve unresolved state
Application restart
→ reconcile unresolved orders
Duplicate event
→ do not double-apply
Order state mismatch
→ pause and reconcile
Unknown state persists
→ alert and block unsafe actions
These cases are much closer to the real operational problem than simply testing:
submit()
returns success
What I want the execution layer to answer
At any point, the system should be able to answer questions like:
What did the strategy intend?
Was the order submitted?
Was it accepted?
Did it match?
How much actually filled?
What remains unresolved?
Did we receive the relevant events?
Does local state agree with remote state?
What position resulted?
Is the resulting exposure within risk limits?
Is it safe to place another order?
Those questions are more useful than a single status field.
They also provide much better information when investigating an incident.
Connecting this to execution verification
This is the reason I keep execution verification separate from the strategy.
The strategy should care about:
Should I trade?
The execution system should care about:
What happened to the order?
The reconciliation layer should care about:
Does local state match reality?
The risk system should care about:
Is the resulting state still acceptable?
And the control plane should care about:
Should the system currently be allowed to trade?
Those are related questions, but they are not the same question.
The architecture I'm building around this
My current Polymarket infrastructure projects reflect that separation.
Polymarket Trading Bot
https://github.com/casatrickdev/polymarket-trading-bot
The broader bot handles strategy, execution, risk, monitoring and backtesting.
Polymarket Execution Verifier
https://github.com/casatrickdev/polymarket-execution-verifier
The verifier focuses on execution state across order, fill, transaction and later settlement stages.
Polymarket Trading Control Plane
https://github.com/casatrickdev/polymarket-trading-control-plane
The control plane focuses on state, health, risk, reconciliation, alerts and operational recovery.
The goal is not to make the system more complicated for its own sake.
It's to keep different kinds of uncertainty from being hidden inside one order handler.
Final takeaway
A trading bot should not think in terms of:
order succeeded
It should know where the order is in its lifecycle.
Something like:
INTENDED
↓
SUBMITTED
↓
ACCEPTED
↓
MATCHED
↓
FILLED
with explicit paths for:
REJECTED
CANCELLED
FAILED
PARTIAL
UNKNOWN
INCONSISTENT
The particularly important state is:
UNKNOWN
because unknown does not mean failed.
It means the system does not yet know what happened.
That should trigger verification or reconciliation, not an automatic assumption.
For an automated Polymarket trading system, that distinction matters because the next order is going to be based on whatever state the system believes right now.
The execution layer needs to make that belief explicit.
And when it cannot establish the truth, the safest state is sometimes simply:
UNKNOWN
↓
STOP
↓
VERIFY
↓
RESUME
That is a much better foundation for a trading system that needs to keep operating reliably.
Top comments (0)