DEV Community

BornToWin
BornToWin

Posted on Originally published at guskarls.substack.com

Polymarket Bot Order-Book Imbalance: How to Detect Real Market Pressure

Order-book imbalance looks simple.

Count the bids.

Count the asks.

Compare them.

If bids are much larger than asks, the market looks bullish.

If asks are much larger than bids, the market looks bearish.

But building a Polymarket bot around that assumption can be a serious mistake.

A large imbalance does not automatically mean the price will move.

Orders can disappear.

New liquidity can arrive.

Trades can consume one side of the book.

The spread can change.

And the imbalance that looked strong 200 milliseconds ago may no longer exist.

The real question is:

How can a Polymarket bot determine whether order-book imbalance represents meaningful market pressure or temporary noise?

That is where the engineering becomes interesting.

What Is Order-Book Imbalance?

For a simple order book, define:

Bid Volume = total size on the bid side

Ask Volume = total size on the ask side

A basic imbalance formula is:

Imbalance =
(Bid Volume - Ask Volume)
/
(Bid Volume + Ask Volume)
Enter fullscreen mode Exit fullscreen mode

The result is between:

-1 and +1
Enter fullscreen mode Exit fullscreen mode

For example:

Bid Volume = 800
Ask Volume = 200
Enter fullscreen mode Exit fullscreen mode

Then:

(800 - 200) / (800 + 200)

= 600 / 1000

= 0.60
Enter fullscreen mode Exit fullscreen mode

The result is:

+0.60
Enter fullscreen mode Exit fullscreen mode

That means the displayed bid-side liquidity is significantly larger than the ask-side liquidity.

But this is only a snapshot.

It is not a trading decision.

Why a Single Imbalance Snapshot Is Dangerous

Imagine your Polymarket bot sees:

Imbalance = +0.70
Enter fullscreen mode Exit fullscreen mode

The bot buys immediately.

Then:

100 ms later → +0.35
200 ms later → +0.10
300 ms later → -0.05
Enter fullscreen mode Exit fullscreen mode

The original imbalance disappeared.

What happened?

Possibilities include:

• Large bids were cancelled

• New asks appeared

• Trades consumed the bids

• Price moved

• Liquidity migrated to another level

The bot wasn't necessarily wrong about the data.

The problem was treating a temporary observation as a persistent signal.

Persistence Matters

This is one of the first improvements I would make to a Polymarket bot.

Instead of asking:

What is the current imbalance?

Ask:

How long has the imbalance remained meaningful?

For example:

t0 → +0.68
t1 → +0.71
t2 → +0.69
t3 → +0.73
t4 → +0.66
Enter fullscreen mode Exit fullscreen mode

This is more interesting than:

t0 → +0.70
t1 → +0.12
t2 → -0.10
Enter fullscreen mode Exit fullscreen mode

Both examples contain:

+0.70
Enter fullscreen mode Exit fullscreen mode

But they describe very different market conditions.

A Better Imbalance Signal

Instead of:

IF imbalance > threshold
    BUY
Enter fullscreen mode Exit fullscreen mode

I prefer thinking in terms of:

Imbalance
+
Persistence
+
Price Movement
+
Trade Flow
+
Liquidity
+
Spread
Enter fullscreen mode Exit fullscreen mode

Then the bot can decide whether the imbalance is strong enough to influence execution.

Which Levels Should You Measure?

Another important question is:

How much of the order book should be included?

Suppose the book is:

Bid

0.48 → 100
0.47 → 200
0.46 → 300
0.45 → 800
0.44 → 1500
Enter fullscreen mode Exit fullscreen mode

and:

Ask

0.52 → 100
0.53 → 150
0.54 → 200
0.55 → 300
0.56 → 400
Enter fullscreen mode Exit fullscreen mode

If you include every level, the distant 0.45 bid can dominate your calculation.

But that liquidity may have very little relevance to the immediate execution price.

So the bot needs to define an observation window.

For example:

Top 1 level
Top 3 levels
Top 5 levels
Price-distance window
Depth within X ticks
Enter fullscreen mode Exit fullscreen mode

There isn't one universally correct choice.

It depends on the strategy and market.

Top-of-Book Imbalance

The simplest approach is to compare only the best bid and best ask sizes.

For example:

Best Bid Size = 500
Best Ask Size = 100
Enter fullscreen mode Exit fullscreen mode

Then:

Imbalance = (500 - 100) / (500 + 100)

= 0.667
Enter fullscreen mode Exit fullscreen mode

This is fast.

But it can also be fragile.

A single large order can make the imbalance look extreme.

If that order disappears, the signal disappears with it.

Multi-Level Imbalance

A more stable approach is to use several levels.

For example:

Bid:

0.50 → 200
0.49 → 150
0.48 → 100

Total = 450
Enter fullscreen mode Exit fullscreen mode

and:

Ask:

0.51 → 100
0.52 → 100
0.53 → 100

Total = 300
Enter fullscreen mode Exit fullscreen mode

Then:

Imbalance =
(450 - 300) / (450 + 300)

= 0.20
Enter fullscreen mode Exit fullscreen mode

This gives a different picture from looking only at the best level.

The important engineering decision is to make the depth window configurable.

Weighting Distance From the Mid Price

Not all liquidity should necessarily have equal weight.

Liquidity close to the current market may matter more than liquidity far away.

So you can introduce weights.

Conceptually:

Weighted Imbalance =
Weighted Bid Depth
-
Weighted Ask Depth
Enter fullscreen mode Exit fullscreen mode

where levels closer to the midpoint receive greater weight.

For example:

Level 1 → weight 1.00
Level 2 → weight 0.75
Level 3 → weight 0.50
Level 4 → weight 0.25
Enter fullscreen mode Exit fullscreen mode

The exact weighting function is something I would test rather than hard-code permanently.

Imbalance vs Price Movement

This is where the signal becomes much more useful.

Suppose:

Imbalance = +0.65
Enter fullscreen mode Exit fullscreen mode

but:

Price = unchanged
Enter fullscreen mode Exit fullscreen mode

That is different from:

Imbalance = +0.65
Price = rising
Enter fullscreen mode Exit fullscreen mode

And different again from:

Imbalance = +0.65
Price = falling
Enter fullscreen mode Exit fullscreen mode

The same imbalance can have different meanings depending on what price is doing.

So a Polymarket bot should not necessarily interpret:

Imbalance = +0.65
Enter fullscreen mode Exit fullscreen mode

in isolation.

Add Trade Flow

Displayed liquidity is not the same thing as executed liquidity.

This is an important distinction.

The order book tells you what traders are currently displaying.

Trades tell you what actually happened.

Imagine:

Bid imbalance = +0.60
Enter fullscreen mode Exit fullscreen mode

but recent trades are repeatedly hitting the bids.

That could indicate that the displayed bid liquidity is being consumed.

Now consider:

Bid imbalance = +0.60
Enter fullscreen mode Exit fullscreen mode

while aggressive buying is repeatedly lifting the asks.

That is a very different situation.

So I would track:

• Order-book imbalance

• Recent trade direction

• Trade volume

• Price movement

• Liquidity changes

Liquidity Appearance vs Liquidity Consumption

This distinction is extremely important.

Suppose a large bid appears:

BUY 1,000
Enter fullscreen mode Exit fullscreen mode

The bot might interpret this as strong support.

But if the order disappears before any meaningful trading occurs, it may have provided little real support.

On the other hand:

BUY 1,000
Enter fullscreen mode Exit fullscreen mode

appears and:

300 traded
400 traded
200 traded
Enter fullscreen mode Exit fullscreen mode

Now the displayed liquidity has actually participated in execution.

This is why I would track both:

Displayed Liquidity
Enter fullscreen mode Exit fullscreen mode

and:

Consumed Liquidity
Enter fullscreen mode Exit fullscreen mode

The Imbalance Can Move Before Price

A useful feature for a Polymarket bot is to monitor the sequence of events.

For example:

1. Bid depth increases
2. Imbalance becomes positive
3. Ask depth decreases
4. Trades begin lifting the ask
5. Mid price moves
Enter fullscreen mode Exit fullscreen mode

This sequence can be more informative than simply observing:

Imbalance = +0.70
Enter fullscreen mode Exit fullscreen mode

The order of events matters.

That means the bot should store short-term market history rather than only the latest book snapshot.

Use a Rolling Window

For example, maintain:

last 100 ms
last 250 ms
last 500 ms
last 1 second
Enter fullscreen mode Exit fullscreen mode

Then calculate:

Current Imbalance
Average Imbalance
Maximum Imbalance
Minimum Imbalance
Imbalance Duration
Imbalance Change
Enter fullscreen mode Exit fullscreen mode

Now the strategy can distinguish:

Strong + persistent
Enter fullscreen mode Exit fullscreen mode

from:

Strong + temporary
Enter fullscreen mode Exit fullscreen mode

Imbalance Velocity

Another useful feature is how quickly imbalance changes.

For example:

t0 = 0.10
t1 = 0.30
t2 = 0.50
t3 = 0.70
Enter fullscreen mode Exit fullscreen mode

The imbalance is increasing rapidly.

Compare that with:

t0 = 0.70
t1 = 0.69
t2 = 0.68
t3 = 0.67
Enter fullscreen mode Exit fullscreen mode

The second case still has positive imbalance.

But the pressure is weakening.

So instead of only calculating:

imbalance
Enter fullscreen mode Exit fullscreen mode

calculate:

imbalance_change
Enter fullscreen mode Exit fullscreen mode

and potentially:

imbalance_velocity
Enter fullscreen mode Exit fullscreen mode

Don't Let Imbalance Override the Strategy

This is one of the most important rules.

I would not build:

IF imbalance > 0.5
    BUY
Enter fullscreen mode Exit fullscreen mode

as the complete strategy.

Instead:

Strategy Signal
+
Order-Book Imbalance
+
Trade Flow
+
Price Movement
+
Liquidity
+
Risk
Enter fullscreen mode Exit fullscreen mode

should influence the final decision.

Imbalance should often be a feature.

Not the entire strategy.

Example: Two Different Markets

Consider Market A:

Imbalance = +0.70
Persistence = 50 ms
Price = falling
Trade flow = selling
Spread = widening
Enter fullscreen mode Exit fullscreen mode

That doesn't look like a strong long setup.

Now Market B:

Imbalance = +0.65
Persistence = 800 ms
Price = rising
Trade flow = buying
Spread = stable
Liquidity = increasing
Enter fullscreen mode Exit fullscreen mode

That is a much more interesting condition.

The numerical imbalance is similar.

The market context is completely different.

Imbalance and Execution

This is where the feature becomes useful for a Polymarket bot.

Suppose the strategy already wants to buy.

The execution engine sees:

Strong positive imbalance
+
Positive trade flow
+
Stable spread
Enter fullscreen mode Exit fullscreen mode

It may decide that waiting for a lower passive price has a higher opportunity cost.

The bot could therefore become more aggressive.

Now consider:

Weakening imbalance
+
Negative trade flow
+
Widening spread
Enter fullscreen mode Exit fullscreen mode

The execution engine may reduce size or wait.

This creates a better relationship between:

Signal

and:

Execution Policy.

Imbalance Can Also Tell the Bot When Not to Trade

This is often overlooked.

A good trading feature doesn't only answer:

"BUY?"

It can also answer:

"WAIT."

For example:

Strategy = BUY

Imbalance = unstable
Spread = widening
Liquidity = falling
Trade flow = conflicting
Enter fullscreen mode Exit fullscreen mode

The best decision may be:

WAIT
Enter fullscreen mode Exit fullscreen mode

The bot doesn't need to trade every time the strategy detects a theoretical opportunity.

A Practical Imbalance Score

You could create a composite score such as:

Score =
w1 × Imbalance
+
w2 × Persistence
+
w3 × TradeFlow
+
w4 × PriceMomentum
+
w5 × LiquidityChange
-
w6 × Spread
Enter fullscreen mode Exit fullscreen mode

The weights should be determined through testing.

The important part is that the model combines multiple pieces of market information.

Then:

Score > Entry Threshold
Enter fullscreen mode Exit fullscreen mode

could permit execution.

While:

Score < Entry Threshold
Enter fullscreen mode Exit fullscreen mode

could result in:

WAIT
Enter fullscreen mode Exit fullscreen mode

Avoid Hard-Coded Thresholds

A common mistake is:

imbalance > 0.50
Enter fullscreen mode Exit fullscreen mode

forever.

Different markets can behave differently.

Liquidity can vary.

Time-to-resolution can vary.

Spread can vary.

Market participants can vary.

So thresholds should ideally be configurable and tested against historical data.

For example:

imbalanceThreshold
minimumPersistence
maxSpread
minimumDepth
minimumTradeVolume
maxSignalAge
Enter fullscreen mode Exit fullscreen mode

These should be strategy parameters, not hidden constants throughout the codebase.

Signal Freshness Matters

Even a strong imbalance can become stale.

A simplified execution flow is:

ORDER BOOK UPDATE
        ↓
FEATURE CALCULATION
        ↓
SIGNAL
        ↓
VALIDATION
        ↓
EXECUTION
Enter fullscreen mode Exit fullscreen mode

If the bot waits too long between those stages, the market state may have changed.

So I would associate each signal with:

signalTimestamp
bookTimestamp
Enter fullscreen mode Exit fullscreen mode

and calculate:

signalAge
Enter fullscreen mode Exit fullscreen mode

If:

signalAge > maximumAllowedAge
Enter fullscreen mode Exit fullscreen mode

the bot should invalidate the signal and recalculate.

A Better Architecture

For a production-style Polymarket bot, I would separate the components:

Market Data
    ↓
Order Book
    ↓
Feature Engine
    ↓
Imbalance Calculator
    ↓
Signal Engine
    ↓
Risk Engine
    ↓
Execution Engine
    ↓
Order Manager
    ↓
Position Manager
Enter fullscreen mode Exit fullscreen mode

The imbalance calculator shouldn't place orders.

The strategy shouldn't directly manipulate balances.

The execution engine shouldn't decide the market direction.

Each component should have a clear responsibility.

What I Would Log

When the bot trades because of an imbalance-related signal, I want to know exactly why.

For example:

SIGNAL

imbalance = 0.64
imbalance_duration = 620ms
trade_flow = +0.72
price_change = +0.004
spread = 0.01
depth = 840
signal_age = 18ms

DECISION

signal = BUY
confidence = HIGH
order_size = 100

EXECUTION

order_type = LIMIT
price = 0.54
filled = 82
remaining = 18
Enter fullscreen mode Exit fullscreen mode

This makes the strategy testable.

Without this information, you may only see:

BUY
SELL
P&L
Enter fullscreen mode Exit fullscreen mode

and have no idea why the bot made its decisions.

Backtesting the Feature

Before trusting order-book imbalance with real capital, I would test:

• Different depth windows

• Different imbalance thresholds

• Different persistence requirements

• Different market conditions

• Different time-to-resolution ranges

• Different liquidity levels

• Different spreads

• Different execution delays

The important metric isn't simply:

"Did price go up after positive imbalance?"

You also need to ask:

"Could the bot actually execute the trade at the assumed price?"

The Difference Between Prediction and Execution

This is the key lesson.

Suppose positive imbalance predicts that price will move higher.

That doesn't automatically mean the bot can profit from it.

The bot still needs:

Prediction
+
Entry Price
+
Execution Probability
+
Slippage
+
Position Size
+
Exit
+
Risk
Enter fullscreen mode Exit fullscreen mode

A feature can be statistically useful and still be economically useless after execution costs.

The Rule I Would Use

I wouldn't build a Polymarket bot around:

Imbalance → Buy
Enter fullscreen mode Exit fullscreen mode

I would build:

Imbalance
      +
Persistence
      +
Trade Flow
      +
Price
      +
Liquidity
      +
Spread
      +
Strategy
      +
Risk
      ↓
Execution Decision
Enter fullscreen mode Exit fullscreen mode

That's a much more robust way to think about order-book imbalance.

Final Thoughts

Order-book imbalance is one of those features that looks extremely simple at first.

Calculate bid volume.

Calculate ask volume.

Compare them.

Done.

But real markets aren't static.

Liquidity changes.

Orders disappear.

Trades consume liquidity.

Prices move.

Spreads change.

Signals become stale.

That's why the useful question isn't:

"Is there an imbalance?"

The better question is:

"Is the imbalance persistent, meaningful, tradable, and consistent with the rest of the market state?"

That is the difference between adding an indicator to a bot and actually engineering an execution system.

What's Next?

I'm continuing this practical Polymarket bot engineering series.

The next problem I want to explore is:

Limit Orders vs Marketable Orders in a Polymarket Bot

When should the bot provide liquidity?

When should it take liquidity?

How much does execution probability matter?

And when is getting filled more important than getting the perfect price?

Source Code

I publish practical Polymarket bot development and experiments on GitHub.

My Polymarket TWAP trading bot:

https://github.com/ruudkoeyvoets/polymarket-trading-bot-twap

Follow the Development

I also share Polymarket bot development, execution experiments, and technical research on YouTube:

https://www.youtube.com/@std0d


This article is for educational and software-development purposes only and is not financial advice. Automated trading involves substantial risk. No strategy, bot, or execution technique guarantees profit.

Top comments (0)