DEV Community

Cover image for Polymarket Trading Bot :Market Maker - How to Backtest Strategies and Optimize Trading Parameters
Benjamin-Cup
Benjamin-Cup

Posted on

Polymarket Trading Bot :Market Maker - How to Backtest Strategies and Optimize Trading Parameters

Day 9 — Building a Python backtesting framework to simulate order execution, measure performance, and optimize market-making strategies.

Introduction

In the previous article, we built an adaptive market-making strategy that adjusts its spreads based on volatility, liquidity, and inventory.

Polymarket Trading Bot

But an important question remains:

How do we know whether our strategy actually works?

Imagine your Polymarket bot places:

  • YES buy order at $0.48
  • YES sell order at $0.52
  • Order quantity: 100 tokens

The theoretical gross profit is:

(0.52 - 0.48) * 100 = $4.00
Enter fullscreen mode Exit fullscreen mode

Looks profitable, right?

But what if:

  • Your buy order never gets filled?
  • Your order is only partially filled?
  • The market moves against you immediately after execution?
  • Other traders are ahead of you in the order queue?
  • Trading costs eliminate your spread?

This is why we need backtesting.

In this article, we'll build the foundation of a Python backtesting framework for a Polymarket market maker bot.

We'll cover:

  1. Backtesting fundamentals
  2. Historical market data
  3. Market-making quote calculations
  4. Limit-order execution simulation
  5. Inventory and P&L tracking
  6. Performance metrics
  7. Parameter optimization
  8. Avoiding overfitting
  9. Preparing for paper trading

1. What Is Backtesting?

Backtesting means testing a trading strategy against historical market data to estimate how it would have performed in the past.

Instead of immediately connecting your bot to a real trading account, you replay historical market conditions and simulate the decisions your bot would have made.

Think of it like a flight simulator.

You can test different situations without risking a real aircraft. Backtesting provides a similar environment for trading strategies.

For example, we can compare three configurations:

Strategy Half-spread Order size
A $0.01 10 tokens
B $0.02 10 tokens
C $0.03 10 tokens

Using the same historical data, we can compare:

  • Number of executed orders
  • Captured spread
  • Inventory exposure
  • Maximum drawdown
  • Net profitability

Backtesting vs. Paper Trading vs. Live Trading

Stage Purpose Real money
Backtesting Replay historical data No
Paper trading Simulate current market conditions No
Live trading Execute real orders Yes

A successful backtest does not guarantee successful live trading. Each stage reveals different problems.


2. Understand the Backtesting Architecture

Before writing code, let's understand the main components.

A basic backtesting system needs six components:

       Historical Market Data
                |
                v
        Market Replay Engine
                |
                v
           Strategy Engine
                |
                v
        Execution Simulator
                |
                v
         Portfolio Manager
                |
                v
       Performance Analyzer
                |
                v
          Backtest Results
Enter fullscreen mode Exit fullscreen mode

Each component has a separate responsibility.

Component Responsibility
Historical Data Stores market snapshots and trades
Replay Engine Replays historical events chronologically
Strategy Engine Calculates bid and ask prices
Execution Simulator Simulates order fills
Portfolio Manager Tracks inventory, cash, and P&L
Performance Analyzer Calculates performance and risk metrics

One important design principle:

The strategy should propose orders, but the execution simulator should decide whether those orders get filled.

This separation makes the system easier to test and improve.


3. Collect Historical Market Data

A backtesting system is only as reliable as its historical data.

For a Polymarket market maker, useful data includes:

Data Purpose
Timestamp Reconstruct market events
YES best bid/ask Track YES market prices
NO best bid/ask Track NO market prices
Bid/ask depth Measure available liquidity
Trade price Track executed prices
Trade size Track executed quantities
Market ID Identify the market
Token ID Identify YES or NO tokens
Tick size Validate quote prices
Fee information Calculate trading costs
Market status Identify active or resolved markets

Why Is Order Book Depth Important?

Consider two markets:

Market A

Price Available quantity
$0.49 1,000
$0.48 800
$0.47 500

Market B

Price Available quantity
$0.49 10
$0.48 5
$0.47 3

Both markets have the same best bid.

However, their liquidity is completely different.

If your bot wants to buy 100 tokens, Market A has much more available liquidity.

This is why using only the best bid and ask is insufficient.

Snapshots vs. Event-Level Data

There are two common approaches:

Order book snapshots

Record the order book at specific intervals.

Event-level data

Record individual changes, such as orders being added, removed, or executed.

Event-level data provides more detailed information and is preferable for realistic execution simulation when available.


4. Calculate Market Metrics and Quotes

Let's start with basic market calculations.

The midpoint is:

midpoint = (best_bid + best_ask) / 2
Enter fullscreen mode Exit fullscreen mode

The spread is:

spread = best_ask - best_bid
Enter fullscreen mode Exit fullscreen mode

For example:

best_bid = 0.48
best_ask = 0.52

midpoint = (best_bid + best_ask) / 2
spread = best_ask - best_bid

print(midpoint)  # 0.50
print(spread)    # 0.04
Enter fullscreen mode Exit fullscreen mode

Now calculate the market-making quotes.

def calculate_quotes(midpoint, half_spread):
    bid = midpoint - half_spread
    ask = midpoint + half_spread

    return {
        "bid": round(bid, 4),
        "ask": round(ask, 4)
    }
Enter fullscreen mode Exit fullscreen mode

Example:

quotes = calculate_quotes(
    midpoint=0.50,
    half_spread=0.02
)

print(quotes)
Enter fullscreen mode Exit fullscreen mode

Output:

{
    "bid": 0.48,
    "ask": 0.52
}
Enter fullscreen mode Exit fullscreen mode

This is our basic quoting model.

In production, we must also validate tick size, order quantity, available balance, market status, and inventory risk.


5. Simulate Realistic Order Execution

This is one of the most important parts of backtesting.

A market price touching your limit order does not automatically mean your order gets filled.

Imagine your bot places a YES buy order at $0.48.

There are already 300 tokens ahead of your order at that price.

Another trader sells 50 tokens at $0.48.

Your order receives nothing because the earlier orders have priority.

This is called queue position.

A simplified execution model:

def calculate_fill_quantity(
    trade_volume,
    quantity_ahead,
    remaining_order_quantity
):
    available = max(
        0,
        trade_volume - quantity_ahead
    )

    return min(
        available,
        remaining_order_quantity
    )
Enter fullscreen mode Exit fullscreen mode

Example:

fill = calculate_fill_quantity(
    trade_volume=150,
    quantity_ahead=100,
    remaining_order_quantity=80
)

print(fill)  # 50
Enter fullscreen mode Exit fullscreen mode

The estimated fill is 50 tokens.

This is only an educational approximation. A realistic simulator must account for actual matching rules, queue changes, and available historical data.

Important Execution Scenarios

Your simulator should eventually support:

  • No fills
  • Full fills
  • Partial fills
  • Multiple partial fills
  • Order cancellations
  • Cancellation delays
  • Order replacements
  • Expired orders
  • Network interruptions

A realistic execution simulator is often more valuable than adding another complicated trading indicator.


6. Track Order States and Inventory

Your backtesting engine needs to understand the lifecycle of each order.

A simplified order state machine:

       NEW
        |
        v
       OPEN
        |
        v
     PARTIAL
      /   \
     v     v
  FILLED  CANCELED
Enter fullscreen mode Exit fullscreen mode

For example, your bot submits an order for 100 YES tokens.

  • First execution: 30 tokens
  • Second execution: 40 tokens
  • Remaining quantity: 30 tokens

Your system must accurately track the executed quantity and remaining quantity.

You also need to track YES and NO inventory separately.

Example:

class Inventory:

    def __init__(self):
        self.yes_quantity = 0
        self.no_quantity = 0

        self.yes_cost = 0.0
        self.no_cost = 0.0

    def buy_yes(self, quantity, price):
        self.yes_quantity += quantity
        self.yes_cost += quantity * price

    def buy_no(self, quantity, price):
        self.no_quantity += quantity
        self.no_cost += quantity * price
Enter fullscreen mode Exit fullscreen mode

Usage:

inventory = Inventory()

inventory.buy_yes(100, 0.48)
inventory.buy_no(50, 0.47)

print(inventory.yes_quantity)  # 100
print(inventory.no_quantity)   # 50
Enter fullscreen mode Exit fullscreen mode

A complete portfolio manager should also track:

  • Average cost basis
  • Sell quantities
  • Realized P&L
  • Unrealized P&L
  • Trading fees
  • Cash balance
  • Resolution payouts

Remember: a market maker is not automatically market-neutral.

Accumulating too many YES or NO tokens can create significant directional exposure.


7. Calculate P&L and Performance Metrics

Let's calculate a simple completed trade.

Assume:

  • Buy price: $0.48
  • Sell price: $0.52
  • Quantity: 100
  • Trading costs: $0.30

Gross P&L:

gross_pnl = (0.52 - 0.48) * 100

print(gross_pnl)  # 4.00
Enter fullscreen mode Exit fullscreen mode

Net P&L:

net_pnl = gross_pnl - 0.30

print(net_pnl)  # 3.70
Enter fullscreen mode Exit fullscreen mode

But total profit is not enough to evaluate a strategy.

We also need to measure risk.

Important Performance Metrics

1. Net P&L

How much money did the strategy make after trading costs?

2. Fill Rate

What percentage of submitted order quantity was executed?

fill_rate = filled_quantity / submitted_quantity
Enter fullscreen mode Exit fullscreen mode

A higher fill rate is not always better. Aggressive quotes can receive more fills but suffer from adverse selection.

3. Maximum Drawdown

Measures the largest decline from a previous portfolio equity peak.

For example:

Time Portfolio equity
1 $1,000
2 $1,100
3 $1,050
4 $950
5 $1,150

The drawdown from $1,100 to $950 is:

drawdown = (1100 - 950) / 1100

print(drawdown)  # 0.1364
Enter fullscreen mode Exit fullscreen mode

That's approximately 13.64%.

4. Adverse Selection

This happens when your order gets filled and the market immediately moves against you.

Example:

  1. Your bot buys YES at $0.50.
  2. The order gets filled.
  3. The midpoint falls to $0.47.
  4. Your position loses approximately $0.03 per token at the new midpoint.

A useful measurement is markout:

buy_markout = midpoint_after - fill_price
sell_markout = fill_price - midpoint_after
Enter fullscreen mode Exit fullscreen mode

Measure markout over multiple time horizons, such as 1, 5, and 30 seconds.

5. Inventory Exposure

Track how much capital is tied up in YES and NO positions.

Also consider the possible resolution outcomes instead of relying only on token quantity differences.


8. Optimize Trading Parameters

Now we can start comparing different strategy configurations.

Some important parameters include:

Parameter Example values
Minimum half-spread 0.01, 0.02, 0.03
Maximum half-spread 0.03, 0.05, 0.08
Order size 5, 10, 20
Maximum inventory 50, 100, 200
Inventory skew 0.0001, 0.0002
Quote refresh interval 1s, 2s, 5s

We can define experiments in Python:

experiments = [
    {
        "name": "baseline",
        "half_spread": 0.02,
        "order_size": 10,
        "max_inventory": 100
    },
    {
        "name": "wider_spread",
        "half_spread": 0.03,
        "order_size": 10,
        "max_inventory": 100
    },
    {
        "name": "smaller_inventory",
        "half_spread": 0.02,
        "order_size": 5,
        "max_inventory": 50
    }
]
Enter fullscreen mode Exit fullscreen mode

Run every configuration against the same historical dataset.

Then compare the results:

Strategy Net P&L Fill rate Max drawdown
Baseline $50 45% 8%
Wider spread $42 30% 4%
Smaller inventory $38 35% 2%

These numbers are hypothetical examples.

The baseline generates the highest profit, but it also has the largest drawdown.

The smaller-inventory strategy earns less but has lower risk.

Don't optimize only for maximum historical profit.

Consider profitability, drawdown, inventory exposure, adverse selection, and consistency.


9. Avoid Overfitting

Overfitting is one of the biggest problems in quantitative trading.

It happens when you adjust a strategy so heavily to historical data that it performs well on past observations but poorly on new market conditions.

A practical solution is to divide historical data into three periods.

Dataset Purpose
Training Develop the strategy
Validation Compare configurations
Testing Evaluate unseen historical data

For example:

  • January–March: Training
  • April: Validation
  • May: Testing

These dates are illustrative.

Keep the periods chronological. Never allow future market information to influence earlier trading decisions.

Walk-Forward Testing

Another useful approach is walk-forward testing.

  1. Tune parameters using an earlier period.
  2. Test the selected configuration on the next unseen period.
  3. Move the testing window forward.
  4. Repeat.
  5. Compare performance across all periods.

This helps determine whether your strategy can adapt to changing market conditions.


10. Account for Realistic Trading Conditions

A good backtest should not assume perfect execution.

Consider these factors:

Factor Why it matters
Trading fees Reduce captured spread
Queue position Orders may not receive fills
Partial fills Orders may execute gradually
Latency Prices can change before execution
Cancellation delays Orders may fill before cancellation completes
Adverse selection Filled orders may move against you
Resolution risk Open positions have final settlement outcomes

I recommend testing three execution scenarios:

Optimistic: Higher fill probability and lower latency.

Base case: Conservative queue and execution assumptions.

Stress case: Lower fill probability, higher costs, and delayed cancellations.

If your strategy only works under optimistic assumptions, it needs further investigation.


11. Organize the Python Project

As the backtesting system grows, avoid putting everything into one Python file.

A modular project structure could look like this:

polymarket-market-maker/
│
├── config/
│   ├── backtest.yaml
│   └── strategy.yaml
│
├── data/
│   ├── historical/
│   └── processed/
│
├── src/
│   ├── data/
│   │   ├── loader.py
│   │   └── validator.py
│   │
│   ├── strategy/
│   │   ├── market_maker.py
│   │   ├── spread_model.py
│   │   └── inventory_skew.py
│   │
│   ├── execution/
│   │   ├── simulator.py
│   │   ├── order.py
│   │   └── fill_model.py
│   │
│   ├── portfolio/
│   │   ├── inventory.py
│   │   └── pnl.py
│   │
│   ├── backtesting/
│   │   ├── engine.py
│   │   └── metrics.py
│   │
│   └── main.py
│
├── tests/
├── results/
├── requirements.txt
└── README.md
Enter fullscreen mode Exit fullscreen mode

Each directory has a clear responsibility.

The main advantage is that the strategy can eventually be reused with different execution adapters:

  • Backtesting execution
  • Paper trading execution
  • Live exchange execution

This makes the system easier to maintain and extend.


12. Common Backtesting Mistakes

Before finishing, remember these common mistakes:

  1. Assuming every order gets filled.
  2. Ignoring trading fees.
  3. Ignoring partial executions.
  4. Using future information.
  5. Optimizing only for historical profit.
  6. Ignoring remaining inventory.
  7. Testing on insufficient historical data.
  8. Treating paper trading as proof of live execution.
  9. Changing too many parameters simultaneously.

A backtest should help you understand where your strategy fails, not just where it succeeds.


Conclusion

Building a Polymarket market maker bot is not just about calculating attractive bid and ask prices.

You also need to understand:

  • Whether your orders can realistically get filled.
  • How much inventory they accumulate.
  • How execution costs affect profitability.
  • How the strategy behaves under different market conditions.
  • Whether its performance remains consistent on unseen data.

Remember these three lessons:

  1. Realistic execution matters more than impressive backtest profits.
  2. Always test your strategy on historical data it has never seen before.
  3. Risk management and inventory exposure matter as much as profitability.

A strategy that survives realistic simulations and unseen historical periods is a stronger candidate for paper trading.

However, even a strong backtest cannot guarantee future profitability.

What's Next? Day 10

Polymarket Market Maker Bot: From Backtesting to Live Trading and Production Monitoring

In the next article, we'll explore:

  • Connecting the strategy to an execution engine.
  • Moving from backtesting to paper trading.
  • Monitoring orders and partial fills.
  • Handling API failures and network interruptions.
  • Managing risk during live operation.
  • Building production monitoring workflows.

Follow the Project

If you're interested in building a Polymarket trading bot with Python, check out the project:

  • GitHub:

    GitHub logo Benj4minCup / Polymarket-Trading-Bot-python

    polymarket Twap Bot polymarket Arbitrage Bot polymarket 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.

    Polymarket-benjamincup-bot-dashboard

    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:


  • Telegram: https://telegram.me/BenjaminCup


Disclaimer: This article is for educational purposes only and does not constitute financial advice. Backtesting and simulated performance do not guarantee future results. Prediction-market trading involves financial risk, including the possibility of losing your entire trading capital.

Top comments (0)