Day 3 of 7: Building a Polymarket Market Maker Bot From Zero to Complete
In the first two parts of this series, we explored what a Polymarket market maker bot is and how the Polymarket order book works.
We covered YES and NO outcome tokens, bid-ask spreads, market depth, limit orders, and the risks of providing liquidity.
Now, let's start building.
In this tutorial, we'll set up a Python project, connect to Polymarket's public market data API, discover available markets, retrieve outcome asset identifiers, and inspect an order book.
By the end, you'll have the foundation of a Python market maker bot that can read market data and calculate basic pricing metrics.
Important: This tutorial is read-only. It does not submit orders, spend funds, or require a private key.
What We'll Build
Our first version will perform five tasks:
- Connect to Polymarket's public API.
- Discover available markets.
- Identify a market's YES and NO assets.
- Retrieve the order book for a selected asset.
- Calculate the best bid, best ask, spread, and midpoint.
The data flow looks like this:
Polymarket Market Data
|
v
Market Discovery
|
v
Select a Market
|
v
Identify YES / NO Assets
|
v
Fetch Order Book
|
v
Calculate Spread & Midpoint
|
v
Print Results
This is a small program, but it establishes an important component of every trading bot: the market data layer.
A production market maker would extend this foundation with real-time data subscriptions, strategy logic, order management, inventory tracking, risk controls, and monitoring.
1. Choose the Python SDK
Polymarket provides developer interfaces for market discovery, order books, pricing, and trading.
For this tutorial, we'll use the unified Python SDK identified in the project references:
Official repository: Polymarket Python SDK
The package used in the examples is polymarket-client.
The SDK is intended to provide access to market information and other Polymarket workflows. Public market-data access does not require you to configure trading credentials for this read-only exercise.
Before installing it, check the repository's current installation instructions and examples. SDK interfaces can change, and code written for an older client may not work with the current version.
2. Set Up Your Development Environment
You can follow this tutorial on Windows, macOS, or Linux.
Check Python
python --version
On some systems, use:
python3 --version
Create a project directory
mkdir polymarket-market-maker
cd polymarket-market-maker
Create a virtual environment
python -m venv .venv
Activate it on Windows PowerShell:
.venv\Scripts\Activate.ps1
On macOS or Linux:
source .venv/bin/activate
Install the SDK
python -m pip install --upgrade pip
pip install polymarket-client
Verify the import:
python -c "from polymarket import PublicClient; print('SDK import successful')"
If the import fails, check the SDK's current Python requirements and installation instructions before proceeding.
3. Create a Clean Project Structure
A common beginner mistake is putting market discovery, pricing calculations, and trading logic into one large Python file.
Let's separate responsibilities from the beginning.
polymarket-market-maker/
├── src/
│ └── polymarket_mm/
│ ├── __init__.py
│ ├── market_data.py
│ ├── orderbook.py
│ ├── pricing.py
│ └── main.py
├── tests/
├── .gitignore
└── README.md
Each module has a specific responsibility:
-
market_data.py— discovers markets and retrieves market information. -
orderbook.py— retrieves and inspects order book levels. -
pricing.py— calculates spread and midpoint. -
main.py— connects the modules and runs the example. -
tests/— contains tests for pricing calculations and other logic.
This structure is intentionally simple. We don't need a complex architecture before our first API request works.
4. Discover Markets Through the SDK
Before inspecting an order book, the bot needs to discover which markets are available.
Create src/polymarket_mm/market_data.py:
from polymarket import PublicClient
def list_markets(limit: int = 10):
"""Return a page of markets from Polymarket."""
with PublicClient() as client:
page = client.list_markets(
closed=False,
page_size=limit,
).first_page()
return page.items
if __name__ == "__main__":
markets = list_markets()
for market in markets:
print(
{
"question": market.question,
"slug": market.slug,
"market_id": market.id,
}
)
Run the module from your project root.
On macOS or Linux:
PYTHONPATH=src python -m polymarket_mm.market_data
On Windows PowerShell:
$env:PYTHONPATH = "src"
python -m polymarket_mm.market_data
The program requests a page of markets and prints basic information about each result.
One important detail: closed=False does not necessarily mean a market is currently accepting orders. A production bot should separately validate market status, trading permissions, and the availability of a usable order book.
For the current implementation, consult the official market discovery example.
5. Understand Market IDs and Asset IDs
A market and a tradable asset are not the same thing.
A market represents an event or question.
An asset identifier identifies a tradable outcome or position associated with that market.
For a conventional binary market, there are typically YES and NO outcomes. However, market infrastructure can evolve, so avoid assuming every market exposes its tradable identifiers in exactly the same format.
The SDK's market model may expose a token ID, position ID, or another identifier depending on the market representation.
The key principle is simple:
Use the current SDK's market model and official examples instead of extracting identifiers from URLs or relying on undocumented raw JSON structures.
You can review the SDK's market helper examples to understand how outcome identifiers are accessed.
6. Retrieve an Order Book
Now let's inspect an actual market.
The following example searches a limited page of open markets until it finds one with a usable YES asset identifier. It then requests the corresponding order book.
Create src/polymarket_mm/orderbook.py:
from polymarket import PublicClient
def inspect_first_available_market():
with PublicClient() as client:
markets = client.list_markets(
closed=False,
page_size=100,
).first_page().items
for market in markets:
# Conventional binary-market example.
# Check the SDK model for your market version.
yes = market.outcomes.yes
asset_id = yes.token_id or yes.position_id
if asset_id is None:
continue
try:
book = client.get_order_book(
asset_id=asset_id
)
except Exception as exc:
print(
f"Skipping market {market.id}: "
f"could not retrieve its book: {exc}"
)
continue
if not book.bids and not book.asks:
continue
return market, asset_id, book
raise RuntimeError(
"No market with a usable YES order book was found. "
"Try again or refine market discovery."
)
def main():
market, asset_id, book = inspect_first_available_market()
print("Market:", market.question)
print("Market ID:", market.id)
print("Asset ID:", asset_id)
print("Number of bids:", len(book.bids))
print("Number of asks:", len(book.asks))
print("\nTop bids:")
for level in sorted(
book.bids,
key=lambda item: item.price,
reverse=True,
)[:5]:
print("Price:", level.price, "Size:", level.size)
print("\nTop asks:")
for level in sorted(
book.asks,
key=lambda item: item.price,
)[:5]:
print("Price:", level.price, "Size:", level.size)
if __name__ == "__main__":
main()
Run it:
PYTHONPATH=src python -m polymarket_mm.orderbook
On Windows PowerShell, use the PYTHONPATH command from the previous section.
The program prints the market question, asset identifier, number of bid and ask levels, and the five best levels on each side.
A few things to remember:
- The best bid is the highest available buy price.
- The best ask is the lowest available sell price.
- The size indicates the quantity available at a price level.
- An empty order book does not provide enough information to calculate both sides of the spread.
The code is an educational starting point, not a guarantee that every market returned by the API will expose these exact fields. Verify the current SDK model before running it.
For another reference, see the official market prices example.
7. Calculate the Spread and Midpoint
Once we have the order book, we can calculate two basic market metrics.
Bid-ask spread
The spread measures the difference between the best ask and the best bid.
[
\text{Spread} = \text{Best Ask} - \text{Best Bid}
]
For example, suppose the best bid is $0.48 and the best ask is $0.52.
The spread is:
0.52 - 0.48 = 0.04
The spread is four cents per share.
Midpoint
The midpoint is the average of the best bid and best ask.
[
\text{Midpoint} =
\frac{\text{Best Bid}+\text{Best Ask}}{2}
]
Using the same example:
(0.48 + 0.52) / 2 = 0.50
The midpoint is $0.50.
Important: The midpoint is a reference price, not a guaranteed execution price. A market order or limit order may execute at a different price depending on liquidity and market conditions.
Implement the calculations
Create src/polymarket_mm/pricing.py:
def calculate_book_metrics(bids, asks):
"""Calculate basic metrics from order book levels."""
best_bid = max(
(float(level.price) for level in bids),
default=None,
)
best_ask = min(
(float(level.price) for level in asks),
default=None,
)
if best_bid is None or best_ask is None:
return {
"best_bid": best_bid,
"best_ask": best_ask,
"spread": None,
"midpoint": None,
}
if best_bid > best_ask:
raise ValueError(
"The book appears crossed; inspect the data "
"and API response before using it."
)
return {
"best_bid": best_bid,
"best_ask": best_ask,
"spread": best_ask - best_bid,
"midpoint": (best_bid + best_ask) / 2,
}
You can import this function into the order book example:
from polymarket_mm.pricing import calculate_book_metrics
metrics = calculate_book_metrics(
book.bids,
book.asks,
)
print("\nBook metrics:")
for name, value in metrics.items():
print(f"{name}: {value}")
For financial calculations and order construction, prefer Decimal or the SDK's decimal-compatible types over binary floating-point arithmetic.
The example uses float for simplicity and educational display.
8. Add Error Handling and Logging
Real-world API requests can fail.
Common causes include:
- Network timeouts.
- Rate limits.
- Invalid asset identifiers.
- Temporary service errors.
- Disconnected WebSocket connections.
- Missing or stale market data.
Different errors require different responses.
A timeout may justify a controlled retry. A rate-limit response should trigger appropriate backoff. An invalid identifier may indicate a market-selection problem.
Stale data is particularly important for a market maker. If the order book is outdated, the bot should not continue generating quotes as if the information were current.
Start with Python's standard logging module:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
logger = logging.getLogger(__name__)
logger.info("Market data module started")
As the project grows, structured logs will help investigate market data updates, order submissions, fills, cancellations, and risk-limit events.
Avoid infinite retry loops. A robust bot should stop making trading decisions when its data cannot be trusted.
9. Why We Are Not Placing Orders Yet
At this stage, our program can retrieve market information, but it is not yet a market maker.
It does not know:
- What prices it should quote.
- How large its orders should be.
- When it should cancel or replace orders.
- How to manage inventory.
- How to control exposure and capital usage.
- How to reconcile partial fills and failed requests.
Adding live order placement before implementing these components would be premature.
A safer development sequence is:
- Retrieve market data reliably.
- Calculate candidate quotes.
- Validate quotes against price and risk limits.
- Simulate decisions using recorded or live market data.
- Implement order lifecycle management.
- Test failures, cancellations, and partial fills.
- Consider controlled live testing only after sufficient validation.
Separating market data from order execution makes the bot easier to test and reduces the risk of an early coding mistake spending real funds.
10. What We Built Today
By completing Day 3, we have established the initial market data layer.
We now have a foundation for:
- Installing and importing the Python SDK.
- Discovering open markets.
- Identifying a usable YES asset identifier.
- Retrieving an order book.
- Inspecting bid and ask levels.
- Calculating spread and midpoint metrics.
- Recognizing the importance of API errors and stale data.
This is the first step toward a more complete market maker bot.
Day 4: Design the Market-Making Strategy
In Day 4, we'll move from observing the market to generating candidate quotes.
We'll explore:
- How to select a reference price.
- How to calculate bid and ask quotes.
- How to set a target spread.
- How to choose order sizes.
- How to adjust quotes based on inventory.
- Why fixed-distance-from-midpoint strategies can perform poorly during rapid price movements.
The objective is to build a strategy layer that can be tested before connecting it to live order execution.
Follow the Project
GitHub repository: Polymarket Trading Bot in Python
YouTube tutorial: Watch the video
Contact and discussion: Telegram — BenjaminCup
Follow the series as we build a Polymarket market maker bot from the fundamentals toward a complete Python implementation.

Top comments (0)