DEV Community

Erik
Erik

Posted on

Managing Real-Time Order Book Updates

Building a reliable market-data engine for Polymarket Trading Bots

For short-duration crypto markets, market data is often more important than the strategy itself.

A bot can have a sophisticated TWAP algorithm, momentum model, or probability engine, but if the underlying order book is stale or incorrect, the strategy can still make completely wrong decisions.

This is especially important for Polymarket BTC and ETH Up/Down markets, where prices can change rapidly and liquidity can disappear within seconds.

A reliable trading system therefore needs to answer three questions continuously:

  1. What does the order book look like right now?
  2. Is my local order book still synchronized with the market?
  3. Can I safely use this data to make a trading decision?

This article explains a practical architecture for managing real-time order-book updates and turning that data into trading signals.


The Basic Architecture

Instead of putting WebSocket processing and trading logic into one loop, separate the system into layers:

Polymarket WebSocket
        │
        ▼
  Market Data Receiver
        │
        ▼
   Update Validator
        │
        ▼
  Local Order Book
        │
        ├── Best Bid / Ask
        ├── Spread
        ├── Depth
        └── Imbalance
                │
                ▼
        Feature / Signal Engine
                │
                ▼
          Risk Management
                │
                ▼
           Order Execution
Enter fullscreen mode Exit fullscreen mode

This separation makes the bot easier to debug, test, and scale.

The WebSocket receiver should focus on receiving data.

The order-book manager should maintain the current market state.

The strategy should consume that state rather than manipulate raw WebSocket messages.


1. Why Real-Time Order Books Matter

Consider a simple example.

Your bot receives:

Best Bid: $0.54
Best Ask: $0.55
Enter fullscreen mode Exit fullscreen mode

The strategy calculates:

Spread = $0.01
Enter fullscreen mode Exit fullscreen mode

and decides that liquidity is good enough to enter a position.

But a few hundred milliseconds later, the real market becomes:

Best Bid: $0.51
Best Ask: $0.57
Enter fullscreen mode Exit fullscreen mode

The market has changed significantly.

If the bot continues using the previous state, it may:

  • Calculate the wrong spread
  • Estimate the wrong probability
  • Detect a false trading opportunity
  • Place an order at an unfavorable price
  • Execute a TWAP schedule based on outdated information

This is why data freshness is part of trading logic.

A market-data engine shouldn't simply ask:

"Did I receive a message?"

It should ask:

"Is the market state I am using still valid?"


2. WebSocket Messages Are Events, Not the Final State

A WebSocket feed can be thought of as a stream of events.

For example:

Update 1
    ↓
Update 2
    ↓
Update 3
    ↓
Update 4
    ↓
...
Enter fullscreen mode Exit fullscreen mode

Your application needs to transform those events into a local state.

Imagine receiving:

BUY @ $0.54 → 100
BUY @ $0.54 → 250
SELL @ $0.55 → 100
Enter fullscreen mode Exit fullscreen mode

Your local representation might eventually look like:

BIDS

$0.54 → 250
$0.53 → 450
$0.52 → 800


ASKS

$0.55 → 100
$0.56 → 300
$0.57 → 600
Enter fullscreen mode Exit fullscreen mode

The strategy shouldn't need to understand every individual message.

Instead, it should be able to ask:

best_bid = book.best_bid()
best_ask = book.best_ask()
Enter fullscreen mode Exit fullscreen mode

This abstraction is extremely useful.


3. Creating a Local Order Book

A simple implementation can use Python dictionaries.

class OrderBook:
    def __init__(self):
        self.bids = {}
        self.asks = {}

    def update_bid(self, price, size):
        if size <= 0:
            self.bids.pop(price, None)
        else:
            self.bids[price] = size

    def update_ask(self, price, size):
        if size <= 0:
            self.asks.pop(price, None)
        else:
            self.asks[price] = size

    def best_bid(self):
        return max(self.bids) if self.bids else None

    def best_ask(self):
        return min(self.asks) if self.asks else None
Enter fullscreen mode Exit fullscreen mode

Now your strategy has a clean interface:

bid = book.best_bid()
ask = book.best_ask()

if bid and ask:
    spread = ask - bid
Enter fullscreen mode Exit fullscreen mode

For a prototype, this approach can be sufficient.

For higher-throughput systems, you may eventually want optimized data structures, but correctness should come before optimization.


4. Snapshot + Incremental Updates

A common order-book design uses two types of information:

Initial Snapshot
       ↓
Build Local Book
       ↓
Incremental Updates
       ↓
Modify Local Book
Enter fullscreen mode Exit fullscreen mode

The initial snapshot establishes the state.

For example:

BIDS

0.54 → 300
0.53 → 500
0.52 → 900

ASKS

0.55 → 200
0.56 → 600
0.57 → 800
Enter fullscreen mode Exit fullscreen mode

Then an incremental update arrives:

0.54 → 250
Enter fullscreen mode Exit fullscreen mode

The local book becomes:

BIDS

0.54 → 250
0.53 → 500
0.52 → 900
Enter fullscreen mode Exit fullscreen mode

There is no reason to rebuild the entire book for every update.

This is one of the basic principles behind efficient order-book processing.


5. Validate Incoming Updates

Your market-data layer shouldn't blindly apply every message.

Before updating the local book, validate the data.

For example:

def validate_update(update):
    if update["price"] <= 0:
        return False

    if update["size"] < 0:
        return False

    if update["side"] not in {"BUY", "SELL"}:
        return False

    return True
Enter fullscreen mode Exit fullscreen mode

You should also consider validating:

  • Message type
  • Timestamp
  • Market identifier
  • Token identifier
  • Sequence number
  • Required fields

If a message is malformed, don't allow it to corrupt the local state.

A robust pipeline looks like:

Incoming Message
       ↓
Validation
   ┌───┴───┐
Valid    Invalid
  ↓         ↓
Apply     Log
Update    + Ignore
Enter fullscreen mode Exit fullscreen mode

6. Sequence Numbers and Missing Updates

If the market-data feed provides sequence numbers, they are extremely valuable.

Suppose your bot receives:

100
101
102
104
Enter fullscreen mode Exit fullscreen mode

There is a missing update:

103
Enter fullscreen mode Exit fullscreen mode

Your local order book may now be wrong.

The safest behavior is generally:

Sequence Gap
     ↓
Mark Book Invalid
     ↓
Stop Trading
     ↓
Rebuild From Snapshot
     ↓
Resume
Enter fullscreen mode Exit fullscreen mode

Don't simply assume that the missing update doesn't matter.

One missed update can potentially affect:

  • Best bid
  • Best ask
  • Spread
  • Depth
  • Imbalance
  • Liquidity estimation
  • Trading signals

For a short-duration market, that can be enough to invalidate a trade.


7. Don't Put Everything Inside the WebSocket Loop

A common beginner implementation looks like:

while True:
    message = websocket.recv()

    update_order_book(message)

    calculate_indicators()

    run_strategy()

    place_order()
Enter fullscreen mode Exit fullscreen mode

The problem is that calculate_indicators(), run_strategy(), or place_order() can take time.

Imagine:

WebSocket update
       ↓
Strategy calculation
       ↓
API request
       ↓
500 ms
Enter fullscreen mode Exit fullscreen mode

During those 500 ms, more market-data updates may arrive.

Your bot can fall behind.

A better architecture separates ingestion from processing:

             WebSocket
                 │
                 ▼
          Fast Receiver
                 │
                 ▼
              Queue
                 │
                 ▼
          Order Book Worker
                 │
                 ▼
             Strategy
Enter fullscreen mode Exit fullscreen mode

For example:

import asyncio


async def websocket_reader(ws, queue):
    async for message in ws:
        await queue.put(message)
Enter fullscreen mode Exit fullscreen mode

And:

async def book_worker(queue, book):
    while True:
        message = await queue.get()

        try:
            book.apply_update(message)
        finally:
            queue.task_done()
Enter fullscreen mode Exit fullscreen mode

The important idea is:

The receiver should remain lightweight.


8. Queue Backpressure

Queues are useful, but they can create another problem.

Suppose your bot receives:

1,000 updates/second
Enter fullscreen mode Exit fullscreen mode

but can only process:

500 updates/second
Enter fullscreen mode Exit fullscreen mode

Then the queue keeps growing:

100
200
300
400
500
600
...
Enter fullscreen mode Exit fullscreen mode

Eventually, the bot isn't processing the current market anymore.

It is processing history.

For real-time trading, this is dangerous.

You should monitor:

Queue size
Message rate
Processing rate
Processing latency
Enter fullscreen mode Exit fullscreen mode

If the system falls significantly behind, it may be safer to rebuild the current book rather than continue processing stale information.


9. Measure Data Freshness

Latency isn't just about how quickly Python executes.

You should measure the age of your market information.

For example:

Exchange event
      ↓
Bot receives
      ↓
Book updated
      ↓
Signal generated
      ↓
Order submitted
Enter fullscreen mode Exit fullscreen mode

Record timestamps for each step.

Example:

Market event:       10:00:00.100
Received:           10:00:00.104
Book updated:       10:00:00.105
Signal generated:   10:00:00.107
Order submitted:    10:00:00.110
Enter fullscreen mode Exit fullscreen mode

Now you can estimate:

Network latency:      4 ms
Book processing:      1 ms
Signal calculation:   2 ms
Execution request:    3 ms
Enter fullscreen mode Exit fullscreen mode

This information becomes extremely useful when optimizing a trading system.


10. Detect Stale Order Books

Your strategy should refuse to trade if the market data becomes too old.

For example:

import time


def is_stale(last_update, max_age=1.0):
    return time.time() - last_update > max_age
Enter fullscreen mode Exit fullscreen mode

Then:

if is_stale(book.last_update):
    return
Enter fullscreen mode Exit fullscreen mode

You can make this more explicit with a health state:

class BookStatus:
    INITIALIZING = "initializing"
    HEALTHY = "healthy"
    STALE = "stale"
    INVALID = "invalid"
    REBUILDING = "rebuilding"
Enter fullscreen mode Exit fullscreen mode

Then the strategy can simply check:

if book.status != BookStatus.HEALTHY:
    return
Enter fullscreen mode Exit fullscreen mode

This is a simple but powerful safety mechanism.


11. Calculate the Spread

Once the book is synchronized, you can calculate the spread.

def get_spread(book):
    bid = book.best_bid()
    ask = book.best_ask()

    if bid is None or ask is None:
        return None

    return ask - bid
Enter fullscreen mode Exit fullscreen mode

Example:

Best Bid = $0.54
Best Ask = $0.56

Spread = $0.02
Enter fullscreen mode Exit fullscreen mode

The spread tells you something about current liquidity and execution conditions.

A strategy might detect a theoretical edge of:

+0.06
Enter fullscreen mode Exit fullscreen mode

but if the spread and expected slippage consume most of that edge, the trade may not be attractive.


12. Look Beyond the Best Price

Best bid and best ask are useful, but they don't describe the entire book.

Consider two markets.

Market A

$0.54 → 50
$0.53 → 100
$0.52 → 150
Enter fullscreen mode Exit fullscreen mode

Market B

$0.54 → 2,000
$0.53 → 3,000
$0.52 → 5,000
Enter fullscreen mode Exit fullscreen mode

Both have the same best bid.

But Market B has dramatically more liquidity.

You can calculate top-level depth:

def bid_depth(book, levels=5):
    bids = sorted(
        book.bids.items(),
        reverse=True
    )[:levels]

    return sum(size for _, size in bids)


def ask_depth(book, levels=5):
    asks = sorted(
        book.asks.items()
    )[:levels]

    return sum(size for _, size in asks)
Enter fullscreen mode Exit fullscreen mode

This gives the strategy a better understanding of how much liquidity is actually available.


13. Order-Book Imbalance

One of the most interesting features you can derive from an order book is Order-Book Imbalance (OBI).

The basic formula is:

                    Bid Volume - Ask Volume
OBI = ------------------------------------------------
                    Bid Volume + Ask Volume
Enter fullscreen mode Exit fullscreen mode

Suppose:

Bid Volume = 900
Ask Volume = 300
Enter fullscreen mode Exit fullscreen mode

Then:

OBI = (900 - 300) / (900 + 300)
    = 0.50
Enter fullscreen mode Exit fullscreen mode

The interpretation is:

+1.0 → strong bid-side dominance
 0.0 → balanced
-1.0 → strong ask-side dominance
Enter fullscreen mode Exit fullscreen mode

Python:

def order_book_imbalance(book, levels=5):
    bids = sorted(
        book.bids.items(),
        reverse=True
    )[:levels]

    asks = sorted(
        book.asks.items()
    )[:levels]

    bid_volume = sum(size for _, size in bids)
    ask_volume = sum(size for _, size in asks)

    total = bid_volume + ask_volume

    if total == 0:
        return 0.0

    return (bid_volume - ask_volume) / total
Enter fullscreen mode Exit fullscreen mode

But there is an important warning:

Order-book imbalance is not a guaranteed prediction of future price.

It should be treated as a feature, not as an automatic buy/sell signal.


14. Use Multiple Imbalance Windows

A single snapshot can be noisy.

Instead, calculate OBI over multiple windows:

OBI 1 second
OBI 3 seconds
OBI 5 seconds
OBI 10 seconds
Enter fullscreen mode Exit fullscreen mode

Then combine them:

signal = (
    0.40 * obi_1s +
    0.30 * obi_3s +
    0.20 * obi_5s +
    0.10 * obi_10s
)
Enter fullscreen mode Exit fullscreen mode

Consider:

1s  = +0.75
3s  = +0.61
5s  = +0.52
10s = +0.45
Enter fullscreen mode Exit fullscreen mode

This indicates relatively persistent buying pressure.

Compare that with:

1s  = +0.80
3s  = +0.05
5s  = -0.10
10s = -0.20
Enter fullscreen mode Exit fullscreen mode

The second pattern may simply represent a temporary burst of liquidity.

This is why time-series features are often more useful than a single snapshot.


15. Connect Order-Book Data With External BTC/ETH Data

This is where the order book becomes especially interesting for Polymarket crypto markets.

Instead of relying only on the Polymarket price, you can combine:

BTC/ETH Price
Momentum
Volatility
Order-Book Imbalance
Distance From Strike
Time Remaining
Polymarket Price
Enter fullscreen mode Exit fullscreen mode

A probability model could produce:

P(UP) = 0.64
Enter fullscreen mode Exit fullscreen mode

while the Polymarket price is:

UP = $0.56
Enter fullscreen mode Exit fullscreen mode

Then:

Potential Edge = 0.64 - 0.56
               = +0.08
Enter fullscreen mode Exit fullscreen mode

The architecture becomes:

BTC / ETH Market Data
          │
          ├── Momentum
          ├── Volatility
          └── Price
                  │
                  ▼
           Probability Model
                  ▲
                  │
        Polymarket Order Book
                  │
          ├── Spread
          ├── Depth
          └── OBI
                  │
                  ▼
             Fair Value
                  │
                  ▼
             Edge Check
                  │
                  ▼
           Trading Decision
Enter fullscreen mode Exit fullscreen mode

Now the order book is not just used for execution.

It becomes part of the signal-generation process.


16. Adaptive TWAP Execution

Reliable real-time order-book data can also improve TWAP execution.

A basic TWAP strategy might execute:

$100
 ↓
10 sec
 ↓
$100
 ↓
10 sec
 ↓
$100
 ↓
10 sec
Enter fullscreen mode Exit fullscreen mode

But market conditions change continuously.

Suppose the model initially detects:

Market price = $0.56
Fair probability = $0.64
Edge = +0.08
Enter fullscreen mode Exit fullscreen mode

The bot begins executing.

Then the market changes:

Fair probability = 0.57
Market price = 0.56
Edge = +0.01
Enter fullscreen mode Exit fullscreen mode

The original opportunity has almost disappeared.

A smarter bot can stop the remaining TWAP orders.

Conceptually:

Strong Edge
     ↓
Continue / accelerate

Normal Edge
     ↓
Normal TWAP

Weak Edge
     ↓
Reduce / stop

Invalid Data
     ↓
Stop immediately
Enter fullscreen mode Exit fullscreen mode

This is much more adaptive than blindly executing a fixed TWAP schedule.


17. Handling WebSocket Disconnects

No real-time system should assume the WebSocket connection will remain alive forever.

A production architecture should support:

CONNECTED
    ↓
Receiving Data
    ↓
Connection Lost
    ↓
Invalidate Local Book
    ↓
Reconnect
    ↓
Load Snapshot
    ↓
Apply New Updates
    ↓
Validate
    ↓
Resume Trading
Enter fullscreen mode Exit fullscreen mode

The important part is:

Don't trade while the local order book is uncertain.

For example:

book.status = "REBUILDING"

await reconnect()

snapshot = await get_snapshot()

book.load_snapshot(snapshot)

book.status = "HEALTHY"
Enter fullscreen mode Exit fullscreen mode

Only after the book is synchronized should the strategy become active again.


18. Monitor the Health of the Market-Data Layer

A serious trading bot should monitor more than PnL.

Useful metrics include:

WebSocket status
Last message time
Last book update
Message rate
Queue depth
Sequence gaps
Book rebuild count
Average latency
Maximum latency
Stale-data events
Enter fullscreen mode Exit fullscreen mode

For example:

Market Data Health

WebSocket:       CONNECTED
Messages/sec:    420
Book latency:    4.2 ms
Queue depth:     0
Sequence gaps:   0
Book status:     HEALTHY
Enter fullscreen mode Exit fullscreen mode

This can immediately tell you whether a strategy problem is actually a market-data problem.


19. Record Raw Market Data

If you're developing trading strategies, historical market data is extremely valuable.

Instead of only storing trades, record the market-data events needed to reconstruct the book.

Then you can build:

Raw Market Data
       ↓
Replay Engine
       ↓
Order Book
       ↓
Features
       ↓
Strategy
       ↓
Backtest
Enter fullscreen mode Exit fullscreen mode

For example, if an OBI strategy loses money, you can replay the exact market conditions.

You might discover:

OBI = +0.60
Enter fullscreen mode Exit fullscreen mode

but five seconds later:

OBI = -0.40
Enter fullscreen mode Exit fullscreen mode

The problem may not be that OBI is useless.

The problem might be that the strategy reacts too slowly to reversals.

That insight is almost impossible to obtain without historical market data.


20. Keep the Trading Path Lightweight

Not every calculation needs to happen for every update.

Separate fast operations:

Best Bid
Best Ask
Spread
Top-Level Depth
OBI
Enter fullscreen mode Exit fullscreen mode

from expensive operations:

Historical volatility
Complex probability models
Machine-learning inference
Large database writes
Detailed analytics
Enter fullscreen mode Exit fullscreen mode

A good architecture is:

              Market Data
                   │
                   ▼
             Fast Book Update
                   │
            ┌──────┴──────┐
            ▼             ▼
       Fast Features   Raw Data
            │             │
            ▼             ▼
        Strategy       Database
            │
            ▼
         Execution
Enter fullscreen mode Exit fullscreen mode

The trading path stays fast while analytics can run asynchronously.


21. Add a Risk Layer

Never go directly from:

Signal
  ↓
Order
Enter fullscreen mode Exit fullscreen mode

Use:

Signal
  ↓
Risk Manager
  ↓
Execution
Enter fullscreen mode Exit fullscreen mode

The risk manager can verify:

Is the order book healthy?
Is the data fresh?
Is the position too large?
Is liquidity sufficient?
Is the market near expiration?
Has the maximum loss been reached?
Enter fullscreen mode Exit fullscreen mode

For example:

def can_trade(book, position, order_size):
    if book.status != "HEALTHY":
        return False

    if is_stale(book.last_update):
        return False

    if position + order_size > MAX_POSITION:
        return False

    return True
Enter fullscreen mode Exit fullscreen mode

This provides another layer of protection when something unexpected happens.


22. Common Mistakes

1. Trading on stale data

A WebSocket can be connected while the local state is still stale.

Solution: track the age of the latest update.

2. Ignoring sequence gaps

One missing update can make the local book incorrect.

Solution: detect gaps and rebuild.

3. Running strategy logic inside the receiver

Heavy processing can cause market-data lag.

Solution: separate ingestion and strategy processing.

4. Looking only at the best bid and ask

You may miss important liquidity information.

Solution: analyze multiple levels of depth.

5. Treating OBI as a guaranteed prediction

Order-book pressure can disappear quickly.

Solution: combine OBI with momentum, volatility, probability, and time remaining.

6. Continuing after a disconnect

Your local book may no longer represent the real market.

Solution: invalidate the book and rebuild it before trading again.


A Practical Architecture

Putting everything together:

                     POLYMARKET
                         │
                         ▼
                  WebSocket Feed
                         │
                         ▼
                 ┌──────────────┐
                 │   Receiver   │
                 └──────┬───────┘
                        │
                        ▼
                    Validator
                        │
                        ▼
                 ┌──────────────┐
                 │ Order Book   │
                 │   Manager    │
                 └──────┬───────┘
                        │
          ┌─────────────┼─────────────┐
          ▼             ▼             ▼
        Spread         Depth          OBI
          │             │             │
          └─────────────┼─────────────┘
                        ▼
                 Feature Engine
                        │
            ┌───────────┴───────────┐
            ▼                       ▼
       BTC/ETH Data          Polymarket Data
            │                       │
            └───────────┬───────────┘
                        ▼
                Probability Model
                        │
                        ▼
                   Edge Engine
                        │
                        ▼
                  Risk Manager
                        │
                        ▼
                 Adaptive TWAP
                        │
                        ▼
                    Execution
Enter fullscreen mode Exit fullscreen mode

And running alongside the entire system:

Connection Monitor
Sequence Monitor
Latency Monitor
Book Health Monitor
Raw Data Recorder
Enter fullscreen mode Exit fullscreen mode

This architecture gives you a reusable foundation for multiple trading strategies.


Final Thoughts

Managing a real-time order book isn't just about receiving WebSocket messages.

The real challenge is maintaining a correct and current representation of the market.

A reliable system should provide:

  • Fast market-data ingestion
  • Local order-book state
  • Snapshot and incremental update handling
  • Sequence validation
  • Stale-data detection
  • Connection recovery
  • Spread and depth calculation
  • Order-book imbalance
  • Latency monitoring
  • Historical data recording
  • Risk controls

Once this foundation is in place, you can build more advanced strategies on top of it:

Order Book
    ↓
Imbalance
    ↓
Momentum
    ↓
Probability
    ↓
Edge
    ↓
Adaptive TWAP
Enter fullscreen mode Exit fullscreen mode

For short-duration BTC and ETH prediction markets, this architecture can be particularly useful because the market can change significantly within seconds.

The important lesson is simple:

A trading strategy is only as good as the market data feeding it.

Build the order-book layer correctly first. Then build the strategy on top of it.

I have developed several automated Polymarket crypto Up/Down trading bots, including the Final Sniper Bot, TWAP Ensure Bot, and other proprietary strategies.

If you’re interested in learning more about these profitable Polymarket trading systems or discussing how they work, feel free to get in touch.

Contact:
https://t.me/erikerik116

Top comments (0)