DEV Community

Benjamin-Cup
Benjamin-Cup

Posted on

Building a Market-Aware Execution Engine for Polymarket Bots with Python

How to build an adaptive execution layer that reacts to spread, liquidity, volatility, and order-book conditions.

A trading bot can have a good strategy and still produce poor execution.

Why?

Because finding a trading opportunity is only one part of the problem.

The bot also needs to decide:

  • How much should it buy?
  • Should it use a passive or aggressive order?
  • Is there enough liquidity?
  • Is the market moving too quickly?
  • Should a large order be split?
  • Should an existing order be cancelled or repriced?

This is where an adaptive execution layer becomes useful.

Instead of using a fixed rule such as:

Signal → Place Order
Enter fullscreen mode Exit fullscreen mode

we can build:

Market Data
     ↓
Market Analyzer
     ↓
Risk Manager
     ↓
Adaptive Router
     ↓
Execution Engine
     ↓
Polymarket
Enter fullscreen mode Exit fullscreen mode

The goal is not to predict the market better.

The goal is to make the execution decision more aware of current market conditions.

This article focuses on engineering and execution architecture. Adaptive execution does not guarantee profitability.


1. Why Execution Matters

Imagine the order book currently looks like this:

YES Bid:  $0.59
YES Ask:  $0.61

Spread:   $0.02
Enter fullscreen mode Exit fullscreen mode

A strategy decides that buying YES is attractive.

But if the bot immediately sends a large aggressive order, it may consume liquidity across multiple price levels.

For example:

$0.61 → 300 shares
$0.62 → 500 shares
$0.63 → 800 shares
$0.64 → 1,000 shares
Enter fullscreen mode Exit fullscreen mode

A 2,000-share order is therefore not necessarily executed at a single price.

The theoretical entry price and the actual execution price can be very different.

This means the execution system needs to understand more than just the current price.

It should monitor:

Spread
Liquidity
Order-book depth
Recent trades
Price movement
Volatility
Order size
Current position
Execution urgency
Enter fullscreen mode Exit fullscreen mode

2. Adaptive Order Routing

A basic trading bot might contain logic like:

place_order(price, size)
Enter fullscreen mode Exit fullscreen mode

The execution method is fixed.

An adaptive router introduces another decision layer:

                Market Data
                     ↓
              Market Analyzer
                     ↓
              Adaptive Router
                     ↓
        ┌────────────┼────────────┐
        ↓            ↓            ↓
     Passive      Aggressive     Split
        │            │            │
        └────────────┼────────────┘
                     ↓
              Execution Engine
                     ↓
                 Polymarket
Enter fullscreen mode Exit fullscreen mode

The router can choose between:

PASSIVE
AGGRESSIVE
SPLIT
WAIT
CANCEL_REPRICE
Enter fullscreen mode Exit fullscreen mode

The important architectural principle is:

The trading signal should decide whether there is an opportunity. The execution layer should decide how to interact with the order book.


3. Building the Market Analyzer

The first component is a market-state analyzer.

A simplified version could calculate the best bid, best ask, spread, and midpoint:

def market_metrics(orderbook):
    bid = orderbook["best_bid"]
    ask = orderbook["best_ask"]

    spread = ask - bid
    mid = (bid + ask) / 2

    return {
        "bid": bid,
        "ask": ask,
        "spread": spread,
        "mid": mid,
    }
Enter fullscreen mode Exit fullscreen mode

A production implementation would maintain considerably more state:

best_bid
best_ask
spread
mid_price
bid_depth
ask_depth
imbalance
recent_volume
recent_trades
price_velocity
volatility
Enter fullscreen mode Exit fullscreen mode

This state becomes the input for the routing decision.


4. Measuring Order-Book Liquidity

Order size should be compared with available liquidity.

For example:

def calculate_depth(book):
    total_bid = sum(
        level["size"]
        for level in book["bids"]
    )

    total_ask = sum(
        level["size"]
        for level in book["asks"]
    )

    return {
        "bid_depth": total_bid,
        "ask_depth": total_ask
    }
Enter fullscreen mode Exit fullscreen mode

However, total depth alone isn't enough.

Consider:

$0.60 → 300 shares
$0.61 → 500 shares
$0.62 → 800 shares
$0.63 → 1,000 shares
Enter fullscreen mode Exit fullscreen mode

A router should understand where the liquidity exists.

For a large order, consuming several levels can significantly change the average execution price.

So an execution engine should estimate the potential cost of walking through the order book before sending a large order.


5. Detecting Market Volatility

The router should also know whether the market is relatively stable or moving rapidly.

A simplified example:

import numpy as np

def calculate_volatility(prices):
    returns = np.diff(prices)
    return np.std(returns)
Enter fullscreen mode Exit fullscreen mode

In a real system, the calculation should use a properly defined time series and sampling period.

The important concept is maintaining a short-term market regime.

For example:

LOW VOLATILITY
      ↓
More passive execution may be possible


HIGH VOLATILITY
      ↓
Re-evaluate more frequently
Enter fullscreen mode Exit fullscreen mode

The exact behavior depends on the trading strategy.


6. Creating the Routing Decision

Now we can combine the market metrics.

A simple router might look like this:

def choose_route(spread, volatility):

    if spread > 0.03:
        return "PASSIVE"

    if volatility > 0.05:
        return "SPLIT"

    return "AGGRESSIVE"
Enter fullscreen mode Exit fullscreen mode

This is intentionally simple.

A production router could incorporate:

Spread
+
Liquidity
+
Volatility
+
Order Size
+
Position Exposure
+
Signal Strength
+
Execution Urgency
Enter fullscreen mode Exit fullscreen mode

The result could be:

PASSIVE
AGGRESSIVE
SPLIT
WAIT
CANCEL_REPRICE
Enter fullscreen mode Exit fullscreen mode

The thresholds should be treated as strategy-specific parameters rather than universal trading values.


7. Turning the Router into a Python Class

A dedicated class makes the execution layer easier to extend:

class AdaptiveRouter:

    def __init__(self):
        pass

    def evaluate(self, spread, volatility):

        if spread > 0.03:
            return "PASSIVE"

        if volatility > 0.05:
            return "SPLIT"

        return "AGGRESSIVE"


router = AdaptiveRouter()

decision = router.evaluate(
    spread=0.02,
    volatility=0.01
)

print(decision)
Enter fullscreen mode Exit fullscreen mode

Output:

AGGRESSIVE
Enter fullscreen mode Exit fullscreen mode

Again, the numbers here are only examples.

The important part is the architecture, not the specific thresholds.


8. Splitting Large Orders

Suppose the strategy wants to buy:

2,000 shares
Enter fullscreen mode Exit fullscreen mode

Instead of submitting everything at once:

BUY 2,000
Enter fullscreen mode Exit fullscreen mode

the router could divide the parent order:

BUY 500
BUY 500
BUY 500
BUY 500
Enter fullscreen mode Exit fullscreen mode

A simple implementation:

def split_order(total_size, chunks):

    chunk_size = total_size / chunks

    return [chunk_size] * chunks
Enter fullscreen mode Exit fullscreen mode

Example:

orders = split_order(
    total_size=2000,
    chunks=4
)

print(orders)
Enter fullscreen mode Exit fullscreen mode

Result:

[500, 500, 500, 500]
Enter fullscreen mode Exit fullscreen mode

But splitting an order doesn't automatically improve execution.

The important part is what happens between child orders.

A better execution loop is:

Parent Order
     ↓
Child Order #1
     ↓
Re-evaluate Market
     ↓
Child Order #2
     ↓
Re-evaluate Market
     ↓
Child Order #3
     ↓
Re-evaluate Market
     ↓
Child Order #4
Enter fullscreen mode Exit fullscreen mode

If market conditions change, the router can change the remaining execution plan.


9. Dynamic Repricing

Another useful component is stale-order management.

Suppose the bot submits:

BUY @ $0.59
Enter fullscreen mode Exit fullscreen mode

Then the market moves:

Bid → $0.60
Ask → $0.62
Enter fullscreen mode Exit fullscreen mode

The execution engine needs to decide whether the original order is still appropriate.

Possible questions include:

Should we keep the order?

Should we cancel it?

Should we reprice it?

Has the trading signal disappeared?

Has volatility increased?

Has liquidity changed?
Enter fullscreen mode Exit fullscreen mode

A simple architecture:

Existing Order
      ↓
Market Changed?
      ↓
   ┌──┴──┐
   │     │
  YES    NO
   │     │
   ↓     ↓
Re-evaluate
           Keep
Enter fullscreen mode Exit fullscreen mode

This prevents the bot from blindly leaving stale orders in a changing market.


10. Risk Management Comes First

The router shouldn't be allowed to bypass risk controls.

For example:

MAX_POSITION = 5000

if current_position >= MAX_POSITION:
    stop_trading()
Enter fullscreen mode Exit fullscreen mode

A daily loss limit could be implemented as:

MAX_DAILY_LOSS = 1000

if daily_pnl <= -MAX_DAILY_LOSS:
    stop_trading()
Enter fullscreen mode Exit fullscreen mode

Liquidity can also act as a filter:

if book_depth < minimum_depth:
    skip_trade()
Enter fullscreen mode Exit fullscreen mode

And a volatility filter could prevent execution during extreme conditions:

if volatility > volatility_threshold:
    stop_trading()
Enter fullscreen mode Exit fullscreen mode

A clean hierarchy is:

Signal
   ↓
Risk Manager
   ↓
Adaptive Router
   ↓
Execution Engine
Enter fullscreen mode Exit fullscreen mode

The router decides how to execute only after the risk layer determines that trading is allowed.


11. Connecting Everything Together

A larger Polymarket bot can be structured like this:

┌────────────────────┐
│    Signal Engine   │
└─────────┬──────────┘
          ↓
┌────────────────────┐
│    Risk Manager    │
└─────────┬──────────┘
          ↓
┌────────────────────┐
│  Adaptive Router   │
└─────────┬──────────┘
          ↓
┌────────────────────┐
│  Execution Engine  │
└─────────┬──────────┘
          ↓
       Polymarket
Enter fullscreen mode Exit fullscreen mode

This separation provides a major engineering advantage.

You can improve the execution system without rewriting the strategy.

For example:

Router V1
Simple spread rules

        ↓

Router V2
Liquidity-aware execution

        ↓

Router V3
Liquidity
+ volatility
+ imbalance
+ execution-cost model
Enter fullscreen mode Exit fullscreen mode

The signal engine can remain unchanged.


12. A Practical Example

Suppose the market currently has:

YES Bid: 0.59
YES Ask: 0.61

Spread: 0.02
Enter fullscreen mode Exit fullscreen mode

The strategy wants:

2,000 shares
Enter fullscreen mode Exit fullscreen mode

The market analyzer reports:

Spread:      Moderate
Liquidity:   Moderate
Volatility:  Low
Position:    Within Limit
Enter fullscreen mode Exit fullscreen mode

The router might choose:

Execution Mode: SPLIT
Enter fullscreen mode Exit fullscreen mode

Then:

500 shares
     ↓
Re-evaluate
     ↓
500 shares
     ↓
Re-evaluate
     ↓
500 shares
     ↓
Re-evaluate
     ↓
500 shares
Enter fullscreen mode Exit fullscreen mode

If the market changes significantly after the first execution, the router doesn't have to blindly continue with the original plan.

This is the core idea:

Don't decide the entire execution path once. Re-evaluate as market conditions change.


13. Measuring Execution Quality

An adaptive router should be evaluated using execution data, not assumptions.

Useful metrics include:

Fill Rate

Filled Orders
─────────────
Submitted Orders
Enter fullscreen mode Exit fullscreen mode

Average Execution Price

Compare the expected entry price with the actual volume-weighted execution price.

Slippage

Actual Execution Price
        -
Reference Price
Enter fullscreen mode Exit fullscreen mode

Execution Latency

Measure:

Signal
  ↓
Order Submitted
  ↓
Order Filled
Enter fullscreen mode Exit fullscreen mode

Partial Fill Rate

How frequently are orders only partially filled?

Cancellation Rate

A router that constantly cancels and replaces orders may create unnecessary execution overhead.

Realized PnL

Execution metrics should ultimately be analyzed together with the strategy's overall performance.

For example, improving fill rate isn't necessarily useful if the average execution price becomes significantly worse.


14. Logging Execution Data

One of the most important parts of developing an adaptive router is collecting enough data to analyze its decisions.

A useful execution record might contain:

timestamp
market
side
signal_price
submitted_price
executed_price
order_size
filled_size
spread
depth
volatility
latency
PnL
Enter fullscreen mode Exit fullscreen mode

This makes it possible to compare different routing policies.

For example:

Strategy A
Passive Execution

vs.

Strategy B
Aggressive Execution

vs.

Strategy C
Adaptive Execution
Enter fullscreen mode Exit fullscreen mode

The comparison shouldn't focus only on historical PnL.

Also examine:

Slippage
Fill probability
Execution latency
Drawdown
Partial fills
Cancellation rate
Market conditions
Parameter sensitivity
Enter fullscreen mode Exit fullscreen mode

This helps determine whether an observed improvement is robust or specific to a particular historical period.


15. Production Architecture

A more complete execution architecture could eventually look like:

                    Market Data
                         │
                         ▼
                  ┌──────────────┐
                  │ Market State │
                  └──────┬───────┘
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
       Spread         Liquidity      Volatility
          │              │              │
          └──────────────┼──────────────┘
                         ▼
                  ┌──────────────┐
                  │ Risk Manager │
                  └──────┬───────┘
                         │
                         ▼
                  ┌──────────────┐
                  │    Router    │
                  └──────┬───────┘
                         │
              ┌──────────┼──────────┐
              ▼          ▼          ▼
           Passive    Aggressive   Split
              │          │          │
              └──────────┼──────────┘
                         ▼
                  ┌──────────────┐
                  │  Execution   │
                  │    Engine    │
                  └──────┬───────┘
                         ▼
                     Polymarket
Enter fullscreen mode Exit fullscreen mode

Each component has one primary responsibility:

Market Data
→ Collect information

Market State
→ Maintain current conditions

Signal Engine
→ Identify opportunities

Risk Manager
→ Decide whether trading is allowed

Adaptive Router
→ Decide how to execute

Execution Engine
→ Submit and manage orders

Analytics
→ Measure results
Enter fullscreen mode Exit fullscreen mode

This modularity makes the system much easier to test and evolve.


16. Final Thoughts

A trading bot is more than a signal generator.

There is an entire execution layer between the strategy and the market.

A fixed system might do:

Signal
  ↓
Order
Enter fullscreen mode Exit fullscreen mode

A more advanced system can do:

Signal
  ↓
Risk
  ↓
Market Analysis
  ↓
Adaptive Routing
  ↓
Order Management
  ↓
Performance Analytics
Enter fullscreen mode Exit fullscreen mode

The router can continuously evaluate:

Spread
Liquidity
Volatility
Order Size
Position
Market State
Execution Urgency
Enter fullscreen mode Exit fullscreen mode

and adjust the execution process accordingly.

The interesting engineering challenge isn't simply making the router more complicated.

It's collecting enough execution data to answer a much more important question:

Which execution decisions actually improve execution under different market conditions?

That is where an execution layer moves from a collection of rules toward a measurable trading-system component.


Open-Source Reference

I maintain a Polymarket trading-bot project where I experiment with trading strategies, execution logic, and related infrastructure.

GitHub:
https://github.com/Benjam1nCup/Polymarket-trading-bot-python-V2

The repository is intended for educational and research purposes. Trading involves substantial risk, and historical or simulated results do not guarantee future performance.


Connect

If you're interested in discussing:

  • Polymarket trading bots
  • Web3 development
  • Execution systems
  • Quantitative trading infrastructure
  • Automated trading architecture

You can find me on Telegram:

@BenjaminCup


This article is for educational purposes only and is not financial advice. Automated trading involves significant risks, including execution losses, liquidity risk, technical failures, and market volatility.

Top comments (0)