Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 7 min read

Why a Polymarket Trading Bot Needs State Reconciliation

A trading bot can keep running while its internal state is already wrong. That is one of the most dangerous failure modes in automated trading. The obvious problems are easy to notice: the process crashes the WebSock

A trading bot can keep running while its internal state is already wrong.

That is one of the most dangerous failure modes in automated trading.

The obvious problems are easy to notice:

  • the process crashes
  • the WebSocket disconnects
  • an API request fails
  • an order is rejected

The harder problems are silent:

  • a partial fill was missed
  • an order was cancelled remotely but remains open locally
  • a reconnect caused an event gap
  • local position state no longer matches exchange state
  • the bot continues trading using stale exposure data

At that point, the bot may still look healthy.

It just isn't trading from reality anymore.

For a serious Polymarket trading system, state reconciliation needs to be a first-class component.

The basic problem

Imagine the bot submits:

BUY 100 contracts @ 0.57

Its local state becomes:

order_id = 123
status   = OPEN
size     = 100
filled   = 0

Then 40 contracts fill.

The correct state is now:

status    = PARTIALLY_FILLED
filled    = 40
remaining = 60

But suppose the WebSocket disconnects at exactly the wrong time.

Your process reconnects.

The event containing that fill is gone.

Your local state still says:

filled = 0
remaining = 100

Now the strategy can make completely different decisions from the ones it should make.

It might:

  • place another order
  • calculate incorrect exposure
  • report incorrect PnL
  • apply the wrong risk limit
  • cancel an order that isn't actually in the state it expects

Nothing necessarily crashes.

That is what makes stale state dangerous.

WebSocket events are not enough

Real-time events are essential for a trading system.

They let the bot react quickly to order and trade changes without constantly polling.

But there is an important architectural distinction:

Events tell you what happened. Reconciliation tells you what is true now.

A production system needs both.

I think about the architecture like this:

                 REAL-TIME PATH

             Polymarket CLOB
                    |
                WebSocket
                    |
            Event Processor
                    |
             Local State
          orders / fills /
          positions / risk


              RECONCILIATION PATH

             Remote API State
                    |
              Reconciliation
                    |
              Local State
                    |
             State Repair

The real-time path optimizes for speed.

The reconciliation path optimizes for correctness.

1. Turn events into explicit state transitions

One mistake is letting every incoming event directly mutate arbitrary application state.

Instead, model the trading lifecycle explicitly.

For example:

ORDER_PLACED
     ↓
OPEN
     ↓
PARTIALLY_FILLED
     ↓
FILLED

Cancellation has its own lifecycle:

OPEN
  ↓
CANCEL_REQUESTED
  ↓
CANCELLED

And failures should also be explicit.

This gives you a state machine that can be tested and replayed.

Instead of asking:

Why does the bot think it owns 400 contracts?

you can ask:

Which state transition caused the position to become 400?

That makes failures much easier to debug.

2. Make event processing idempotent

Real-time trading systems eventually encounter:

  • duplicate events
  • retries
  • reconnects
  • replayed messages
  • process restarts

Processing the same event twice should not create two fills.

A simplified example:

def process_event(event):
    if already_processed(event.id):
        return

    apply_transition(event)
    mark_processed(event.id)

The exact implementation depends on the system, but the principle is important:

The same event should produce the same state exactly once.

This becomes especially important when rebuilding state after a restart.

3. Reconcile after reconnects

A reconnect shouldn't simply mean:

connect
β†’ subscribe
β†’ resume trading

A safer flow is:

disconnect
    ↓
reconnect
    ↓
load persisted state
    ↓
query current remote state
    ↓
compare local vs remote
    ↓
repair differences
    ↓
verify risk
    ↓
resume trading

For example:

LOCAL                     REMOTE

Order 123: OPEN           Order 123: OPEN
Filled: 0                 Filled: 40

Order 456: FILLED         Order 456: FILLED
Position: +100            Position: +100

The reconciliation layer identifies:

Order 123:
local filled  = 0
remote filled = 40

β†’ local state is stale
β†’ repair local state

Only after that should the strategy continue.

4. Separate orders, fills, and positions

These are related, but they are not the same thing.

A useful hierarchy is:

Orders
   ↓
Fills
   ↓
Positions
   ↓
Exposure
   ↓
Risk

Consider:

Order A: BUY 100
Order B: BUY 50

Then:

Order A β†’ filled 60
Order B β†’ filled 50

The position isn't 150.

It is based on the actual executed fills.

This distinction becomes critical once you have partial fills, multiple orders, cancellations, and concurrent strategy actions.

A robust trading system should never derive actual exposure from intended order sizes alone.

5. Order lifecycle is not a single state

Another common mistake is treating:

order submitted

as equivalent to:

order executed

They are different stages.

A more realistic lifecycle is:

INTENDED
   ↓
SUBMITTED
   ↓
ACCEPTED
   ↓
MATCHED
   ↓
FILLED

With alternative paths:

SUBMITTED β†’ REJECTED
SUBMITTED β†’ CANCELLED
MATCHED   β†’ RETRYING

The strategy layer needs to know which state it is actually in.

This becomes particularly important in asynchronous execution systems, where an order can be accepted before the full execution lifecycle is known.

6. Persist state

If the process restarts, memory disappears.

A production system should persist enough state to reconstruct what happened.

At minimum, that usually means tracking things like:

order ID
market
side
requested size
filled size
remaining size
status
timestamps
event IDs
trade IDs

For positions:

market
position size
average entry
realized PnL
unrealized PnL
last update

The goal isn't to persist every internal object.

The goal is to preserve enough information that the system can:

  1. restart
  2. inspect the remote state
  3. reconcile
  4. continue safely

7. Detect gaps instead of assuming everything arrived

A WebSocket connection can be alive while your application is still missing information.

That means your system should have some notion of continuity.

For example:

event 1001
event 1002
event 1004

Where is:

event 1003?

A trading system shouldn't silently assume that the missing event doesn't matter.

This is where sequence tracking, timestamps, persisted event IDs, or explicit resynchronization logic can become useful.

The exact mechanism depends on the stream and API.

The architectural principle is the important part:

Don't confuse β€œsocket connected” with β€œstate synchronized.”

8. Reconciliation should be safe

Reconciliation itself can create dangerous behavior if implemented badly.

Suppose local state says:

position = +100

but remote state says:

position = +40

The solution isn't necessarily:

position = 40

You first need to understand why the states differ.

Was there:

  • a missed fill?
  • a delayed event?
  • a duplicate event?
  • an unexpected trade?
  • a stale API response?
  • a bug in local processing?

A useful reconciler should produce a discrepancy such as:

Position mismatch

Local:
  +100

Remote:
  +40

Difference:
  -60

Reason:
  unmatched fill / state gap

Action:
  repair + log + alert

That makes reconciliation observable and debuggable.

9. Add a trading safety gate

One of the most useful patterns is to make reconciliation part of trading permission.

Conceptually:

if not state.is_consistent():
    trading_enabled = False

Then:

NORMAL
  ↓
STATE MISMATCH
  ↓
TRADING PAUSED
  ↓
RECONCILIATION
  ↓
STATE VERIFIED
  ↓
TRADING RESUMED

This is much safer than allowing the strategy to keep trading while its view of exposure is uncertain.

You don't always need to shut down the entire system.

Depending on the risk model, you could:

  • pause new entries
  • allow exits only
  • reduce position size
  • disable a particular market
  • require manual approval

The important part is that strategy execution becomes conditional on trusted state.

10. Recovery should be designed before failure

A common development pattern is:

β€œWe'll deal with reconnects later.”

That usually becomes painful once the system has real positions.

A better architecture defines recovery from the start:

process starts
     ↓
load persisted state
     ↓
connect
     ↓
synchronize
     ↓
reconcile
     ↓
validate risk
     ↓
start strategy

And after a failure:

failure
   ↓
reconnect
   ↓
detect possible gap
   ↓
reconcile
   ↓
repair
   ↓
resume

Recovery is not a special case.

It is part of normal trading-system behavior.

A practical architecture

Putting the pieces together:

                  POLYMARKET CLOB
                         |
              +----------+----------+
              |                     |
          WebSocket             API / Reads
              |                     |
              v                     v
       +--------------+      +--------------+
       | Event        |      | Reconciliation|
       | Processor    |      | Engine        |
       +------+-------+      +------+---------+
              |                     |
              +----------+----------+
                         |
                         v
                +------------------+
                | Persisted State  |
                |                  |
                | Orders           |
                | Fills            |
                | Positions        |
                | Exposure         |
                +--------+---------+
                         |
                         v
                +------------------+
                | Strategy Engine  |
                +--------+---------+
                         |
                         v
                +------------------+
                | Risk Controls    |
                +--------+---------+
                         |
                         v
                +------------------+
                | Execution Engine |
                +------------------+

The strategy is only one component.

The state layer is what allows the strategy to operate safely.

Backtesting should test this too

Most backtests focus on the strategy:

signal
β†’ order
β†’ profit

Production systems need more failure scenarios.

For example:

WebSocket disconnect
Partial fill
Duplicate event
Missed event
Delayed API response
Process restart
Order cancellation race
State mismatch

These can be simulated.

A good trading-system test should ask:

What happens if the world becomes inconsistent for 10 seconds?

That's often much more useful than another percentage-point improvement in a strategy backtest.

The bigger lesson

The most dangerous trading-system bug isn't necessarily a crash.

A crashed bot is obvious.

A bot that continues operating with the wrong view of reality is much harder to notice.

That is why state reconciliation deserves to be treated as a first-class subsystem.

The architecture should assume that:

  • events can be delayed
  • connections can fail
  • fills can be partial
  • processes can restart
  • local state can become stale
  • APIs can behave unexpectedly

And the system should know how to recover.

Final takeaway

When building a Polymarket trading bot, it's tempting to focus on the strategy:

signal
β†’ buy
β†’ sell
β†’ profit

Production systems look more like:

market data
β†’ event processing
β†’ state
β†’ reconciliation
β†’ strategy
β†’ risk
β†’ execution
β†’ persistence
β†’ recovery

The strategy determines what you want to do.

The infrastructure determines whether you know what is actually happening.

That's why I would treat state reconciliation as a core part of any serious Polymarket trading system.

Fast execution matters.

Correct state matters more.

What I'd build next

The natural next component after reconciliation is a replayable Polymarket event stream.

If every order, fill, position change, and reconciliation event can be persisted and replayed, you can use the same infrastructure for:

  • debugging
  • backtesting
  • incident analysis
  • simulation
  • strategy research
  • production recovery

That's when the trading system starts becoming a reusable piece of infrastructure rather than a single-purpose bot.

This article focuses on production trading-system architecture and reliability. Proprietary strategy logic and parameters are intentionally omitted.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.