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
we can build:
Market Data
↓
Market Analyzer
↓
Risk Manager
↓
Adaptive Router
↓
Execution Engine
↓
Polymarket
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
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
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
2. Adaptive Order Routing
A basic trading bot might contain logic like:
place_order(price, size)
The execution method is fixed.
An adaptive router introduces another decision layer:
Market Data
↓
Market Analyzer
↓
Adaptive Router
↓
┌────────────┼────────────┐
↓ ↓ ↓
Passive Aggressive Split
│ │ │
└────────────┼────────────┘
↓
Execution Engine
↓
Polymarket
The router can choose between:
PASSIVE
AGGRESSIVE
SPLIT
WAIT
CANCEL_REPRICE
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,
}
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
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
}
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
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)
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
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"
This is intentionally simple.
A production router could incorporate:
Spread
+
Liquidity
+
Volatility
+
Order Size
+
Position Exposure
+
Signal Strength
+
Execution Urgency
The result could be:
PASSIVE
AGGRESSIVE
SPLIT
WAIT
CANCEL_REPRICE
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)
Output:
AGGRESSIVE
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
Instead of submitting everything at once:
BUY 2,000
the router could divide the parent order:
BUY 500
BUY 500
BUY 500
BUY 500
A simple implementation:
def split_order(total_size, chunks):
chunk_size = total_size / chunks
return [chunk_size] * chunks
Example:
orders = split_order(
total_size=2000,
chunks=4
)
print(orders)
Result:
[500, 500, 500, 500]
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
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
Then the market moves:
Bid → $0.60
Ask → $0.62
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?
A simple architecture:
Existing Order
↓
Market Changed?
↓
┌──┴──┐
│ │
YES NO
│ │
↓ ↓
Re-evaluate
Keep
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()
A daily loss limit could be implemented as:
MAX_DAILY_LOSS = 1000
if daily_pnl <= -MAX_DAILY_LOSS:
stop_trading()
Liquidity can also act as a filter:
if book_depth < minimum_depth:
skip_trade()
And a volatility filter could prevent execution during extreme conditions:
if volatility > volatility_threshold:
stop_trading()
A clean hierarchy is:
Signal
↓
Risk Manager
↓
Adaptive Router
↓
Execution Engine
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
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
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
The strategy wants:
2,000 shares
The market analyzer reports:
Spread: Moderate
Liquidity: Moderate
Volatility: Low
Position: Within Limit
The router might choose:
Execution Mode: SPLIT
Then:
500 shares
↓
Re-evaluate
↓
500 shares
↓
Re-evaluate
↓
500 shares
↓
Re-evaluate
↓
500 shares
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
Average Execution Price
Compare the expected entry price with the actual volume-weighted execution price.
Slippage
Actual Execution Price
-
Reference Price
Execution Latency
Measure:
Signal
↓
Order Submitted
↓
Order Filled
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
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
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
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
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
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
A more advanced system can do:
Signal
↓
Risk
↓
Market Analysis
↓
Adaptive Routing
↓
Order Management
↓
Performance Analytics
The router can continuously evaluate:
Spread
Liquidity
Volatility
Order Size
Position
Market State
Execution Urgency
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)