A Polymarket trading bot can be connected, receiving market data, submitting orders, and still be unsafe to operate.
The problem isn't always the strategy.
It can be the system around the strategy.
A production trading system needs to know:
- what orders are actually open
- what has actually filled
- what positions are actually held
- whether market data is still fresh
- whether local state matches remote state
- whether risk limits are still satisfied
- whether the system should be allowed to place another order
That leads to a different way of thinking about a trading bot.
Instead of:
market data
↓
strategy
↓
order
I want the architecture to look more like:
market data
↓
event processing
↓
state
↓
risk
↓
execution
↓
reconciliation
↓
monitoring
↓
recovery
I'm building a small Polymarket Trading Control Plane around that idea.
The goal is not another trading strategy.
The goal is to build the operational layer that allows a strategy to run with a much clearer understanding of its own state.
Why build a control plane?
When you're prototyping a trading bot, it's easy to keep everything in one process.
Something like:
while True:
market = get_market_data()
signal = strategy(market)
if signal:
place_order(signal)
This is fine for exploring an idea.
But once the system has real orders and positions, the questions become different.
What happens when the WebSocket disconnects?
What happens when an order partially fills?
What happens when the process restarts?
What happens when a response is delayed?
What happens when local state says one thing and the remote state says another?
These are operational problems, not strategy problems.
And they become more important as the amount of automation increases.
GitHub: Polymarket Trading Control Plane
The first principle: local state is not reality
Suppose a strategy submits:
BUY 100
The local application records:
order_id = 123
status = OPEN
size = 100
filled = 0
Then 40 contracts fill.
The real state is now:
status = PARTIALLY_FILLED
filled = 40
remaining = 60
Now imagine the process misses that update.
The process may still believe:
filled = 0
remaining = 100
Nothing has necessarily crashed.
The program is still running.
The strategy is still calculating.
The system just has the wrong state.
That is much more dangerous than a visible exception because the system can continue making decisions from incorrect information.
WebSockets are useful, but they are not your entire state model
Polymarket's current Rust CLOB client supports WebSocket streams for orderbooks, prices, and authenticated user order/trade events. The current V2 client also exposes Data and Gamma API integrations and other CLOB functionality.
That's exactly what a low-latency trading system needs.
But I don't want my architecture to assume:
WebSocket connected
=
state is correct
Those are two different conditions.
I think about the system as having two paths.
POLYMARKET
│
┌──────────┴──────────┐
│ │
WebSocket API / Reads
│ │
▼ ▼
Event Processor Reconciliation
│ │
└──────────┬──────────┘
▼
Local State
The first path is optimized for speed.
The second path is responsible for validating and repairing state.
State should be explicit
One of the first design decisions in the control plane is to separate the major concepts.
I don't want one giant BotState object that mixes everything together.
Instead:
Orders
↓
Trades / Fills
↓
Positions
↓
Exposure
↓
Risk
Each layer answers a different question.
Orders
What did the system request?
order_id
market
side
price
requested_size
status
Fills
What actually executed?
trade_id
order_id
executed_size
execution_price
timestamp
Positions
What do we currently hold?
market
size
average_entry
realized_pnl
unrealized_pnl
Exposure
What risk is currently on the book?
market_exposure
total_exposure
concentration
Risk
Should the system be allowed to place another order?
allowed
paused
degraded
killed
Keeping these concepts separate makes the system easier to reason about.
Model order lifecycles explicitly
Another common mistake is treating an order as simply:
OPEN
or:
CLOSED
Real execution is more complicated.
A simplified lifecycle can look like:
INTENDED
↓
SUBMITTED
↓
ACCEPTED
↓
MATCHED
↓
FILLED
With other possible paths:
SUBMITTED → REJECTED
SUBMITTED → CANCELLED
MATCHED → RETRYING
The exact lifecycle depends on the interface and execution path, but the architecture should allow for intermediate states.
This is particularly relevant with Polymarket's evolving asynchronous execution model. The current V2 Rust client changelog notes that matched orders can carry tradeIDs rather than immediately returning transaction hashes, and the client can resolve those hashes afterward.
That is an important architectural lesson:
An accepted order isn't necessarily the end of the execution lifecycle.
Event processing should be idempotent
Real-time systems eventually see duplicates, reconnects, retries, and restarts.
So event processing needs to be safe to repeat.
A simplified version:
def process_event(event):
if already_processed(event.id):
return
apply_transition(event)
mark_processed(event.id)
The production implementation can use a stronger persistence and transaction model, but the principle remains:
Processing the same event twice should not create two executions.
This becomes even more important if the application needs to replay events after a restart.
Reconciliation is the recovery mechanism
The main job of reconciliation is simple:
Compare what the application believes with what the remote system currently reports.
For example:
LOCAL
Order 123
filled = 0
REMOTE
Order 123
filled = 40
The reconciler identifies:
difference = 40
and creates an explicit discrepancy:
STATE_MISMATCH
order_id: 123
local_filled: 0
remote_filled: 40
action: repair
I don't want this to be a hidden correction somewhere inside the strategy.
It should be its own subsystem.
Reconciliation after a reconnect
A reconnect shouldn't automatically mean:
connected
→ resume trading
I'd rather use:
disconnect
↓
reconnect
↓
load persisted state
↓
read current remote state
↓
compare
↓
repair
↓
verify risk
↓
resume
That extra step is where a lot of production safety comes from.
The same pattern applies after a process restart.
process starts
↓
load state
↓
synchronize
↓
reconcile
↓
validate
↓
trade
Recovery becomes part of normal operation instead of an emergency patch.
The control plane should be able to stop trading
One of the most useful ideas in this project is that trading permission should be separate from the strategy.
For example:
Strategy says:
BUY
doesn't necessarily mean:
System says:
ALLOW
The flow becomes:
Strategy signal
↓
Risk check
↓
Health check
↓
State check
↓
Execution permission
If the system detects a critical issue:
state mismatch
market data stale
risk limit exceeded
execution failures
the answer can be:
TRADING_PAUSED
This is much safer than forcing every strategy to implement its own emergency behavior.
A simple risk model
The first version doesn't need a complicated risk engine.
A few explicit limits already create a useful safety layer.
For example:
max_position_size: 500
max_market_exposure: 1000
max_total_exposure: 5000
max_daily_loss: 250
max_consecutive_failures: 5
max_market_data_age_ms: 5000
Then the decision engine can be:
RISK CHECK
│
┌──────────┼──────────┐
│ │ │
position exposure data age
limit limit limit
│ │ │
└──────────┼──────────┘
│
┌───────┴────────┐
│ │
SAFE UNSAFE
│ │
ALLOW PAUSE
The important part is that the rules exist independently from the trading strategy.
That makes them reusable.
Kill switch
Every automated trading system should have a simple way to stop placing new orders.
The control plane should expose something like:
PAUSE
RESUME
KILL
RECONCILE
A kill switch might be triggered by:
- operator action
- daily loss threshold
- abnormal exposure
- persistent state mismatch
- repeated execution failure
- severe market-data staleness
Conceptually:
KILL SWITCH
│
▼
STOP NEW ORDERS
│
▼
FREEZE STRATEGY
│
▼
ALERT OPERATOR
The exact behavior around existing positions should be configurable.
The important thing is that a runaway strategy shouldn't require the developer to manually terminate the process.
Health is more than uptime
A process being alive doesn't mean a trading system is healthy.
I want health to include things like:
WebSocket connection
Market-data freshness
Order-stream freshness
API availability
Event processing latency
Last reconciliation
Position consistency
Risk state
A useful state might look like:
SYSTEM: DEGRADED
Market data: OK
Order stream: OK
API connectivity: OK
Position state: MISMATCH
Last reconciliation: 38s ago
Risk state: WARNING
Trading: PAUSED
That's much more actionable than:
process = running
Observability should follow the trading lifecycle
The system should eventually be able to answer:
What happened to this order?
A good execution timeline might look like:
10:00:01.120 strategy signal
10:00:01.132 risk approved
10:00:01.145 order submitted
10:00:01.180 order accepted
10:00:01.240 partial fill 40
10:00:01.242 position updated
10:00:01.245 websocket disconnected
10:00:03.101 websocket reconnected
10:00:03.120 reconciliation started
10:00:03.141 state mismatch detected
10:00:03.150 state repaired
10:00:03.170 trading resumed
This is incredibly useful for debugging.
It's also useful for research.
And eventually it can become useful for post-trade analysis.
Why replay matters
There's another reason to persist events.
Once the system stores important state transitions, you can replay them.
historical events
↓
event replay
↓
rebuild state
↓
inspect behavior
That gives the same infrastructure several uses:
- debugging
- recovery
- backtesting
- incident analysis
- strategy research
- simulation
A trading system that can replay its own decisions is much easier to understand than one that only stores the final PnL.
A small architecture
The first version of the project is intentionally small.
polymarket-trading-control-plane/
crates/
├── ingest/
├── events/
├── state/
├── reconcile/
├── risk/
├── health/
├── alerts/
└── control/
examples/
├── monitor/
├── reconcile/
└── simulated_failure/
tests/
├── state/
├── reconciliation/
├── risk/
└── recovery/
The core flow:
POLYMARKET
│
┌──────────┴──────────┐
│ │
WebSocket APIs
│ │
▼ ▼
Event Ingestion Reconciliation
│ │
└──────────┬──────────┘
▼
State Store
│
┌─────────┼─────────┐
▼ ▼ ▼
Risk Health Alerts
│ │ │
└─────────┼─────────┘
▼
Control Plane
│
▼
Strategy /
Execution
This isn't intended to become a full trading platform on day one.
The objective is to make a few critical problems explicit and testable.
Failure scenarios I want to test
A useful test suite should simulate more than successful trades.
For example:
WebSocket disconnect
disconnect
→ reconnect
→ reconcile
→ resume
Partial fill
order size = 100
fill = 40
remaining = 60
Duplicate event
event 1001
event 1001
result:
one state transition
Missed event
1001
1002
1004
detect gap
→ reconcile
Process restart
persisted state
→ process restart
→ reload
→ reconcile
→ continue
Risk breach
exposure > limit
→ trading paused
Stale market data
last update > threshold
→ affected strategy disabled
These scenarios are more valuable to me than simply showing that an order can be submitted successfully.
Where this fits in a trading system
The control plane isn't the strategy.
It's the layer that protects the strategy.
That means different strategies can sit above the same infrastructure:
STRATEGIES
momentum arbitrage market making
\ | /
\ | /
▼ ▼ ▼
┌───────────────────┐
│ CONTROL PLANE │
│ │
│ state │
│ reconciliation │
│ risk │
│ health │
│ recovery │
└─────────┬─────────┘
│
▼
EXECUTION
│
▼
POLYMARKET
That's the direction I find more interesting.
The strategy can change.
The operational infrastructure can remain.
Why I'm building this
I've spent a lot of time looking at the strategy side of automated Polymarket trading.
But the more realistic the system becomes, the more obvious the engineering problems around the strategy become.
A profitable signal doesn't solve:
- stale state
- partial fills
- reconnects
- inconsistent positions
- risk breaches
- operational failures
Those need their own architecture.
And that's the part I want to explore here.
The bigger lesson
The most dangerous trading-system failure isn't always a crash.
A crashed process is obvious.
A process that keeps running while believing the wrong thing is much harder to detect.
That's why I think a serious Polymarket trading system needs more than:
signal
→ order
→ profit
It needs:
market data
→ events
→ state
→ reconciliation
→ risk
→ execution
→ monitoring
→ recovery
The strategy answers:
What should I do?
The control plane answers:
Can I safely do it right now?
And the reconciliation layer answers the question underneath both:
Do I actually know what state the system is in?
That's the problem this project is designed to solve.
What's next
The next step is to make the event stream replayable.
Once important order, fill, position, risk, and reconciliation events can be stored and replayed, the same infrastructure can support:
- debugging
- historical analysis
- recovery
- simulation
- backtesting
- strategy research
That's when the control plane becomes more than a monitoring tool.
It becomes part of the trading system itself.
This project focuses on trading-system infrastructure, reliability, and operational controls. Proprietary trading strategies and implementation details are intentionally omitted.
Top comments (0)