Day 4 of 7 — Building a Polymarket Market Maker Bot From Zero to Complete
How does a trading bot decide when to buy, when to sell, and which prices to use?
In the previous articles in this series, we explored what a Polymarket trading bot is, how prediction market order books work, and how to retrieve market data using Python.
Today, we're moving from reading the order book to calculating our own quotes.
We'll build a simple market-making strategy that calculates bid and ask prices, adjusts quotes based on inventory, and validates its output before any order reaches the exchange.
By the end of this tutorial, you'll understand:
- How to calculate a reference price from the order book
- How bid-ask spreads work
- How inventory affects market-making decisions
- How to implement inventory-aware pricing in Python
- How to write unit tests for your strategy
- What needs to happen before connecting the strategy to live order execution
This is an educational implementation, not a production-ready trading bot. We'll keep the pricing logic separate from order execution so we can test it independently.
1. What Is a Market-Making Strategy?
Imagine a Polymarket market asking whether Bitcoin will finish above a particular price at a specified time.
The YES token currently has the following order book:
| Order book item | Price |
|---|---|
| Best bid | $0.48 |
| Best ask | $0.52 |
| Midpoint | $0.50 |
The best bid is the highest price someone is currently offering to pay. The best ask is the lowest price someone is currently willing to accept.
Our bot wants to place its own orders.
For example:
- Buy YES at $0.49.
- Sell YES at $0.51.
These prices would improve on the existing bid and ask in this hypothetical example, provided the market's tick size and other trading rules permit them.
But should the bot always use these prices?
No.
The market changes constantly. Liquidity can disappear, competing orders can arrive, and the bot can accumulate more tokens than it wants to hold.
A market-making strategy converts market data, trading parameters, and current inventory into desired quotes.
The goal is to provide liquidity while managing execution risk and inventory exposure. A positive spread alone does not guarantee a profit.
2. Calculate the Reference Price
Before generating quotes, we need a reference price.
For a simple market maker, we can start with the midpoint between the best bid and the best ask.
The formula is:
[
M = \frac{B + A}{2}
]
Where:
- (M) = midpoint
- (B) = best bid
- (A) = best ask
Using our example:
[
M = \frac{0.48 + 0.52}{2} = 0.50
]
Our reference price is $0.50.
The midpoint is easy to calculate, but it has limitations. It doesn't necessarily represent the token's true value, and it may not be executable at meaningful size.
A more advanced strategy could incorporate order book imbalance, recent price movements, volatility, and other signals to estimate a fair value.
For now, we'll use the midpoint to keep the implementation simple.
Important: If market data is stale, the book is empty, or the best bid is greater than or equal to the best ask, the bot should stop generating quotes until the data has been validated.
3. Calculate the Bid and Ask
A basic market maker places its bid below the reference price and its ask above it.
We can express this as:
[
\text{Bid} = M - h
]
[
\text{Ask} = M + h
]
Here, (h) represents the target half-spread.
Suppose our parameters are:
- Midpoint: $0.50
- Half-spread: $0.02
The result is:
| Parameter | Value |
|---|---|
| Midpoint | $0.50 |
| Half-spread | $0.02 |
| Target bid | $0.48 |
| Target ask | $0.52 |
| Total spread | $0.04 |
The total spread is:
[
S = \text{Ask} - \text{Bid}
]
Therefore:
[
S = 0.52 - 0.48 = 0.04
]
This gives us a simple starting point for calculating quotes.
However, a fixed spread ignores market conditions. A narrow spread might be competitive in a liquid market but risky during rapid price movements. A wide spread might protect against some risks but make our orders less competitive.
Let's look at how inventory changes the calculation.
4. Why Inventory Management Matters
Imagine our bot starts with zero YES tokens.
It places:
- A buy order for 100 YES tokens at $0.48.
- A sell order at $0.52.
The buy order fills, but the sell order doesn't.
Our bot now holds 100 YES tokens.
If it continues placing the same buy and sell quotes, it could accumulate even more tokens without reducing its existing position.
This is called inventory risk.
A market maker must consider not only the price of its next trade but also how that trade changes its overall position.
When inventory grows, the strategy might:
- Make its buy quote less competitive.
- Adjust its sell quote to encourage inventory reduction.
- Reduce order sizes.
- Stop placing additional buy orders at its maximum inventory limit.
These are separate controls. Adjusting prices can influence trading behavior, but it cannot guarantee that inventory will decrease.
5. Implement Inventory-Aware Pricing
Let's introduce a simple inventory adjustment.
Instead of generating quotes directly from the midpoint, we'll adjust our reference price first:
[
R = M - qk
]
Where:
- (R) = inventory-adjusted reference price
- (M) = market midpoint
- (q) = current inventory, measured in tokens
- (k) = inventory adjustment per token
Positive inventory shifts the reference price downward. Negative inventory shifts it upward, assuming inventory is measured relative to a target position of zero.
For example:
- Midpoint = $0.50
- Inventory = 100 YES tokens
- Inventory adjustment = $0.0001 per token
Then:
[
R = 0.50 - (100 \times 0.0001)
]
[
R = 0.49
]
Our adjusted reference price is $0.49.
With a half-spread of $0.02:
[
\text{Bid} = 0.49 - 0.02 = 0.47
]
[
\text{Ask} = 0.49 + 0.02 = 0.51
]
Compare the original and adjusted quotes:
| Quote | Before adjustment | After adjustment |
|---|---|---|
| Bid | $0.48 | $0.47 |
| Ask | $0.52 | $0.51 |
Both quotes have moved downward.
The lower bid makes the bot less eager to acquire additional YES tokens, while the lower ask may make selling more competitive.
This is a simplified inventory-skew model. In a production strategy, the adjustment should be calibrated to inventory limits, volatility, liquidity, time to market resolution, and the cost of reducing exposure.
6. Write the Pricing Function in Python
Now let's implement the pricing logic.
We'll use Python's Decimal type instead of floating-point numbers because trading prices require predictable decimal arithmetic.
Create a file called strategy.py:
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True)
class Quote:
bid: Decimal
ask: Decimal
def calculate_quotes(
midpoint: Decimal,
half_spread: Decimal,
inventory: Decimal,
inventory_skew: Decimal,
min_price: Decimal = Decimal("0.01"),
max_price: Decimal = Decimal("0.99"),
) -> Quote:
"""
Calculate inventory-aware candidate quotes.
Positive inventory shifts both quotes downward.
Negative inventory shifts both quotes upward.
This function calculates prices only.
It does not submit live orders.
"""
if not min_price < max_price:
raise ValueError("Invalid price bounds")
if not min_price <= midpoint <= max_price:
raise ValueError("Midpoint is outside allowed bounds")
if half_spread <= 0:
raise ValueError("Half-spread must be positive")
if inventory_skew < 0:
raise ValueError("Inventory skew must not be negative")
adjusted_reference = (
midpoint - inventory * inventory_skew
)
bid = adjusted_reference - half_spread
ask = adjusted_reference + half_spread
# Clamp candidate prices to configured bounds.
bid = max(min_price, min(bid, max_price))
ask = max(min_price, min(ask, max_price))
if bid >= ask:
raise ValueError(
"Quotes are invalid after applying price bounds"
)
return Quote(bid=bid, ask=ask)
if __name__ == "__main__":
quote = calculate_quotes(
midpoint=Decimal("0.50"),
half_spread=Decimal("0.02"),
inventory=Decimal("100"),
inventory_skew=Decimal("0.0001"),
)
print("Candidate bid:", quote.bid)
print("Candidate ask:", quote.ask)
Run the script:
python strategy.py
Expected output:
Candidate bid: 0.4700
Candidate ask: 0.5100
We now have a function that transforms market conditions and inventory into candidate quotes.
Notice what the function doesn't do:
- It doesn't retrieve market data.
- It doesn't check whether the order book has changed.
- It doesn't place or cancel orders.
- It doesn't check account balances.
- It doesn't guarantee that either quote can be executed.
Keeping these responsibilities separate makes the strategy easier to understand and test.
Implementation note: Clamping prices to a range is not enough to validate an actual order. Production code must also handle tick-size rounding, minimum order sizes, crossing the book, and the case where a valid bid and ask cannot be produced.
7. Order Size Is Just as Important as Price
Suppose two bots use the same quotes.
Bot A places orders for 5 tokens.
Bot B places orders for 1,000 tokens.
Their prices are identical, but their inventory exposure is very different.
If the buy orders fill, Bot B can accumulate a much larger position before the bot has an opportunity to react.
A basic order-sizing policy should consider:
- Available capital
- Current inventory
- Maximum allowed position
- Market liquidity
- Minimum order requirements
- Potential costs of exiting the position
- Multiple orders filling before cancellations complete
For an initial implementation, use a small fixed order size and enforce a strict maximum inventory.
For example, if the bot has reached its maximum allowed YES inventory, the risk manager should prevent it from submitting additional buy orders.
This control should be enforced independently of the quote calculation. A valid price does not automatically mean the bot should be allowed to trade.
8. Validate Quotes Against Polymarket's Trading Rules
Our Python function calculates decimal prices, but a real Polymarket order must satisfy the selected market's current trading rules.
A candidate price such as $0.4837 may not be valid for a market with a different tick size.
Before submitting an order, the bot should:
- Retrieve the market's current trading parameters.
- Round prices to valid increments using a deliberate rounding policy.
- Validate price bounds and order-size precision.
- Check token balances and available funds.
- Check whether the order would cross the current book.
- Confirm that the market is still open for trading.
- Verify inventory and risk limits.
Avoid hardcoding a single tick size or minimum order size for every market.
Also, the book may change between calculation and submission. Local validation reduces avoidable errors, but it cannot eliminate execution races.
9. Separate Strategy, Risk, and Execution
A market-making bot is easier to maintain when its responsibilities are separated.
A useful architecture looks like this:
Market Data
|
v
Quote Strategy
|
v
Risk Manager
|
v
Order Manager
|
v
Polymarket Trading Interface
|
v
Order and Fill Updates
|
v
Inventory State
Each component has a distinct responsibility.
Market Data
Retrieves order book updates and maintains a reliable view of current market conditions.
Quote Strategy
Calculates the desired bid and ask prices.
Risk Manager
Checks maximum inventory, order size, available capital, and other trading limits.
Order Manager
Decides whether to submit, retain, cancel, or replace existing orders.
Execution Tracking
Processes order updates, partial fills, completed fills, and cancellations, then reconciles the internal state with the actual account state.
For example, the strategy might calculate a bid of $0.47. But if the current best ask has moved to $0.46, submitting that bid could execute immediately instead of resting passively.
The order manager needs to detect this situation and follow the strategy's execution policy.
Likewise, if inventory has reached its maximum, the risk manager must block additional buy orders even if their prices are valid.
This separation is especially important when moving from a local Python experiment to a live trading system.
10. Test the Strategy Before Live Trading
Before connecting the strategy to an order manager, test its behavior independently.
Install pytest:
pip install pytest
Create test_strategy.py:
from decimal import Decimal
from strategy import calculate_quotes
def test_zero_inventory_produces_symmetric_quotes():
quote = calculate_quotes(
midpoint=Decimal("0.50"),
half_spread=Decimal("0.02"),
inventory=Decimal("0"),
inventory_skew=Decimal("0.0001"),
)
assert quote.bid == Decimal("0.48")
assert quote.ask == Decimal("0.52")
def test_positive_inventory_shifts_quotes_down():
neutral = calculate_quotes(
midpoint=Decimal("0.50"),
half_spread=Decimal("0.02"),
inventory=Decimal("0"),
inventory_skew=Decimal("0.0001"),
)
long_position = calculate_quotes(
midpoint=Decimal("0.50"),
half_spread=Decimal("0.02"),
inventory=Decimal("100"),
inventory_skew=Decimal("0.0001"),
)
assert long_position.bid < neutral.bid
assert long_position.ask < neutral.ask
def test_negative_inventory_shifts_quotes_up():
neutral = calculate_quotes(
midpoint=Decimal("0.50"),
half_spread=Decimal("0.02"),
inventory=Decimal("0"),
inventory_skew=Decimal("0.0001"),
)
short_position = calculate_quotes(
midpoint=Decimal("0.50"),
half_spread=Decimal("0.02"),
inventory=Decimal("-100"),
inventory_skew=Decimal("0.0001"),
)
assert short_position.bid > neutral.bid
assert short_position.ask > neutral.ask
Run the tests:
pytest -q
These tests verify three basic properties:
- Zero inventory produces symmetric quotes around the midpoint.
- Positive inventory shifts both quotes downward.
- Negative inventory shifts both quotes upward.
You should also test invalid inputs, extreme inventory, price boundaries, and cases where clamping would make the bid equal to or greater than the ask.
Unit tests verify that the function behaves as intended. They do not demonstrate that the strategy is profitable.
11. How Can We Evaluate Market-Making Performance?
Once the pricing function passes its tests, the next step is to replay recorded order book data and simulate order execution.
A useful simulation should account for:
- Orders that never fill
- Partial fills
- Queue priority
- Cancellation delays
- Price movements after a fill
- Trading fees and other applicable costs
- Inventory accumulation
- The cost of reducing or closing a position
One important risk is adverse selection.
Imagine the bot buys YES at $0.48. A moment later, new information causes the token's fair value to fall to $0.42.
The bot may have earned the apparent spread on paper in other trades, but this particular fill creates an unrealized loss.
That is why spread size alone is not a sufficient measure of performance.
Track metrics such as:
- Realized and unrealized P&L
- Inventory over time
- Fill rate
- Average spread captured
- Fees and execution costs
- Adverse price movement after fills
- Maximum inventory exposure
A credible evaluation should also account for the outcome of prediction-market positions at resolution. For a YES token, the final payout depends on the market's resolution rules, not simply on the last quoted price.
Use historical replay and paper trading to identify problems before considering live deployment.
12. What We've Built
In this lesson, we moved from observing the order book to calculating inventory-aware quotes.
We implemented a small Python strategy that:
- Calculates bid and ask prices from a midpoint
- Applies an inventory adjustment
- Uses
Decimalfor price calculations - Checks basic price bounds
- Can be tested without connecting to a live exchange
We also explored order sizing, market validation, risk management, execution architecture, and testing.
The key lesson is that calculating a price is only one part of building a market maker. A reliable system must also manage inventory, validate orders, track execution, and respond to changing market conditions.
What's Next? Day 5 — Automated Quoting and Order Management
In Day 5, we'll move from calculating candidate quotes to managing orders.
We'll explore how to:
- Submit limit orders through Polymarket's supported trading interface
- Track open orders and their status
- Cancel or replace outdated quotes
- Handle partial fills and uncertain API responses
- Avoid duplicate orders
- Reconcile internal state with actual account state
The goal is to connect our strategy to an order manager without losing control of inventory or execution state.
Follow the Project
GitHub repository: [Polymarket Trading Bot — Python]
Benj4minCup
/
Polymarket-Trading-Bot-python
Polymarket Trading Bot Twap Bot polymarket Arbitrage Bot
Polymarket Trading bot system
An open-source and Strong Strategy collection of Polymarket trading bot and Polymarket arbitrage bot and Polymarket TWAP trading bot in Python for high-performance automated trading on polymarket crypto 5min and 15min markets.
This repository is primarily intended for educational and research purposes. It includes strategy concepts, implementation approaches, and selected performance screenshots to help developers understand how different automated trading strategies can be designed and tested.
The repository does not provide a complete production-ready trading bot source code. Instead, it provides strategy descriptions and research materials that you can use as a foundation for developing your own system.
If you are interested in building a Polymarket Trading Bot, you can follow my tutorials and use the concepts in this repository to develop your own implementation.
For users who prefer a ready-to-deploy solution or require custom strategy development, commercial bot development and customization are also available.
Features
…YouTube : [Watch the Polymarket trading bot tutorial]
Telegram: [Join the discussion]
https://telegram.me/BenjaminCup
Follow the seven-day series as we build a Polymarket market maker bot, from understanding prediction markets to implementing pricing logic, order management, risk controls, and deployment.
Disclaimer: This tutorial is for educational purposes only and is not financial advice. Market making involves financial risk. The example strategy has not been demonstrated to be profitable. Test thoroughly with historical data and paper trading before considering live execution.

Top comments (1)
splitting strategy from risk from execution is the whole game here. inventory-aware pricing is the part most tutorials skip.