DEV Community

Benjamin-Cup
Benjamin-Cup

Posted on

Building Polymarket Trading Bot: From Backtesting to Live Trading(Market Maker)

Day 10 — Polymarket Trading Bot Development Series

A market-making strategy can perform well in historical simulations. But can it survive in a real market?

Moving from backtesting to live trading introduces a completely different set of engineering challenges.

Your bot must do more than calculate bid and ask prices. It needs to communicate with the exchange, manage orders, handle partial fills, recover from network failures, monitor inventory, and protect capital.

Polymarket Trading Bot

Consider this situation:

  1. Your bot submits a BUY order for 100 YES tokens.
  2. The exchange fills 40 tokens.
  3. The remaining 60 tokens are still open.
  4. The market price suddenly changes.
  5. Your bot requests cancellation.
  6. The network connection times out.

What should happen next?

Should the bot assume the order was canceled? Should it submit another order?

Making the wrong decision could create unexpected exposure.

In this article, we'll explore how to build a safer production architecture for a Python-based Polymarket market maker.

We'll cover:

  • Backtesting vs. paper trading vs. live trading
  • Production system architecture
  • Separating strategy from execution
  • Order lifecycle management
  • API errors and network recovery
  • Risk management
  • Production monitoring
  • Database persistence
  • VPS deployment

1. Understand the Three Stages of Trading

Before deploying a trading bot, understand these three development stages.

Stage 1: Backtesting

Backtesting uses historical market data to simulate how your strategy would have performed.

Typical operations include:

  • Replaying historical YES and NO prices.
  • Calculating bid and ask prices.
  • Simulating order fills.
  • Tracking inventory.
  • Calculating historical P&L.

The goal is to evaluate your strategy using past market conditions.

However, historical simulations cannot perfectly reproduce real exchange execution.

Stage 2: Paper Trading

Paper trading runs your strategy against current market data without submitting real orders.

For example:

Market midpoint: 0.50

Bot calculates:
BUY YES:  0.48
SELL YES: 0.52

Paper Trading:
BUY YES: 0.48
Status: Simulated Open Order

Market changes:
Best Ask: 0.48

Simulated Fill:
20 YES tokens
Enter fullscreen mode Exit fullscreen mode

No real money is involved.

Paper trading helps test:

  • Live market data connections.
  • Strategy calculations.
  • Order management.
  • Inventory tracking.
  • Logging and monitoring.
  • Recovery after restarting the bot.

However, simulated fills cannot perfectly reproduce real queue priority, matching behavior, or exchange latency.

Stage 3: Live Trading

Live trading connects your bot to the actual exchange and submits real orders.

Now your system must handle:

  • Real account balances.
  • Actual order execution.
  • Partial fills.
  • Trading fees.
  • API failures.
  • Network interruptions.
  • Cancellation requests.
  • Financial risk.

Important: Never move directly from backtesting to large live orders.


2. Design a Production Architecture

A common mistake is putting the entire trading system into one Python file.

A better approach is separating responsibilities into independent components.

Here is a recommended architecture:

              Polymarket
                  |
                  v
          Market Data Service
                  |
                  v
            Strategy Engine
                  |
                  v
             Risk Manager
                  |
                  v
           Order Management
                  |
                  v
           Execution Adapter
                  |
                  v
              Polymarket
                  |
                  v
           Order / Trade Events
                  |
                  v
           Portfolio Manager
                  |
             +----+----+
             |         |
             v         v
         Monitoring  Database
             |         |
             +----+----+
                  |
                  v
              Dashboard
Enter fullscreen mode Exit fullscreen mode

Let's understand the responsibilities.

Market Data Service

Collects order book updates, best bid and ask prices, trades, token identifiers, and market status.

Strategy Engine

Contains the market-making logic. It calculates reference prices, spreads, inventory adjustments, and desired order sizes.

Risk Manager

Checks whether proposed orders are allowed before they reach the exchange.

Order Management

Tracks order creation, open orders, partial fills, cancellation requests, and order replacement.

Execution Adapter

Communicates with the exchange API and translates internal order instructions into exchange requests.

Portfolio Manager

Tracks available cash, YES and NO inventory, average cost, realized P&L, unrealized P&L, fees, and open-order exposure.

Monitoring

Tracks bot health, active orders, market data freshness, API latency, errors, and risk-limit violations.

Separating these responsibilities makes the system easier to test, debug, and maintain.


3. Separate Strategy from Execution

One of the most important architectural decisions is separating the strategy engine from the execution engine.

Why?

Because the same strategy should work in three different environments:

  • Historical backtesting
  • Paper trading
  • Live trading

The strategy should decide what it wants to do.

The execution layer should decide how to perform that action.

For example, define an internal order model:

from dataclasses import dataclass
from decimal import Decimal
from typing import Literal

@dataclass
class OrderIntent:
    token_id: str
    side: Literal["BUY", "SELL"]
    price: Decimal
    quantity: Decimal
Enter fullscreen mode Exit fullscreen mode

This object describes a proposed order. It does not submit anything to Polymarket.

Next, define an execution interface:

from typing import Protocol

class ExecutionClient(Protocol):

    async def submit_order(
        self,
        order: OrderIntent
    ) -> str:
        ...

    async def cancel_order(
        self,
        order_id: str
    ) -> None:
        ...

    async def get_open_orders(self):
        ...

    async def get_balances(self):
        ...
Enter fullscreen mode Exit fullscreen mode

Different execution implementations can follow the same interface:

          ExecutionClient
                 |
       +---------+---------+
       |         |         |
       v         v         v
    Backtest   Paper     Live
    Client     Client    Client
Enter fullscreen mode Exit fullscreen mode

The strategy doesn't need to know which execution environment is being used.

This makes testing much easier because you can evaluate the same strategy without risking real capital.

Note: This is an internal interface example, not a claim that Polymarket's SDK provides these exact method names.


4. Build a Safe Order Lifecycle Manager

Order management is one of the most important parts of a market-making system.

Imagine your bot submits a BUY order for 100 YES tokens.

The exchange reports:

Original Quantity: 100
Filled Quantity:    30
Remaining Quantity: 70
Status: PARTIALLY_FILLED
Enter fullscreen mode Exit fullscreen mode

The strategy recalculates its quotes and decides to cancel the original order.

But cancellation takes time.

During that period, another 20 tokens might be filled.

Your final filled quantity is now 50, not 30.

Your bot must correctly handle this situation.

Recommended internal order states

             NEW
              |
              v
          SUBMITTING
              |
              v
             OPEN
              |
       +------+------+
       |             |
       v             v
PARTIALLY_FILLED  CANCEL_PENDING
       |             |
       |        +----+----+
       |        |         |
       v        v         v
    FILLED   CANCELED  PARTIALLY_FILLED
                         |
                         v
                       FILLED
Enter fullscreen mode Exit fullscreen mode

The exact exchange statuses may differ. These are suggested internal states.

The most important distinction is:

Requesting cancellation is not the same as confirming cancellation.

For example, an order may receive additional fills while cancellation is pending.

Your system must process the final exchange state rather than assume that the cancellation request immediately stopped execution.

Also, every execution should have a unique identifier so duplicate trade events cannot increase inventory twice.


5. Handle API Errors and Network Failures

Live trading systems must expect failures.

Common problems include:

  • Network timeouts
  • API rate limits
  • Authentication failures
  • Insufficient balances
  • Exchange maintenance
  • WebSocket disconnections
  • Delayed responses
  • Unexpected API responses

One particularly dangerous situation happens when an order submission times out.

Consider this:

Bot submits BUY order for 100 YES
                |
                v
       Exchange accepts order
                |
                v
       Network connection fails
                |
                v
          Bot receives timeout
                |
                v
       Bot submits another order
                |
                v
       Potential duplicate exposure
Enter fullscreen mode Exit fullscreen mode

The bot doesn't know whether the original order was accepted.

Blindly retrying could create two orders instead of one.

Use an uncertain-order state

A safer workflow is:

          SUBMITTING
               |
               v
             TIMEOUT
               |
               v
       SUBMISSION_UNKNOWN
               |
               v
      RECONCILE WITH EXCHANGE
               |
         +-----+-----+
         |     |     |
         v     v     v
       FOUND NOT FOUND
               |
               v
         STILL UNCERTAIN
Enter fullscreen mode Exit fullscreen mode

When a submission response is uncertain:

  1. Do not immediately submit another identical order.
  2. Check the exchange's current order state.
  3. Search using available order identifiers.
  4. Reconcile open orders and recent trades.
  5. Retry only when there is sufficient evidence that the original request was not accepted.

Use client-generated identifiers or idempotency mechanisms only where officially supported by the exchange API.

For read operations, exponential backoff can help:

import asyncio

async def retry_read_operation(
    operation,
    max_attempts=3
):
    for attempt in range(max_attempts):
        try:
            return await operation()

        except TimeoutError:
            if attempt == max_attempts - 1:
                raise

            await asyncio.sleep(2 ** attempt)
Enter fullscreen mode Exit fullscreen mode

Do not apply this same blind retry approach to financial actions such as order creation.


6. Implement Risk Management

A profitable strategy is not automatically a safe strategy.

The risk manager should have the final decision about whether an order can be submitted.

Here are some essential controls.

Maximum order size

Prevent individual orders from becoming too large.

def validate_order_size(
    quantity,
    maximum_quantity
):
    return quantity <= maximum_quantity
Enter fullscreen mode Exit fullscreen mode

Maximum inventory

Set separate limits for YES and NO positions.

def validate_inventory(
    current_quantity,
    proposed_quantity,
    maximum_quantity
):
    return (
        current_quantity + proposed_quantity
        <= maximum_quantity
    )
Enter fullscreen mode Exit fullscreen mode

A complete implementation must also account for sells, existing orders, pending cancellations, and potential fills.

Maximum capital allocation

Don't allow the strategy to use all available funds just because it finds trading opportunities.

Example configuration:

risk:
  max_capital_allocation: 100
  max_order_value: 10
  max_yes_inventory: 100
  max_no_inventory: 100
  max_open_orders: 10
  max_daily_loss: 20
Enter fullscreen mode Exit fullscreen mode

These values are illustrative, not financial recommendations.

Stale market data protection

Imagine your WebSocket disconnects.

Your bot still remembers that the market midpoint was 0.50, but the actual market has moved to 0.42.

Continuing to submit orders using old prices can create unnecessary risk.

A simple freshness check:

import time

def is_market_data_fresh(
    last_update_timestamp,
    maximum_age_seconds=3
):
    age = time.time() - last_update_timestamp

    return age <= maximum_age_seconds
Enter fullscreen mode Exit fullscreen mode

If market data becomes too old, stop submitting new quotes until the feed is restored and reconciled.

Emergency stop

Every production trading bot needs a way to stop new trading activity.

class TradingSafety:

    def __init__(self):
        self.enabled = True

    def stop(self):
        self.enabled = False

    def can_trade(self):
        return self.enabled
Enter fullscreen mode Exit fullscreen mode

Remember that stopping new orders and canceling existing orders are two different operations.

Your emergency-stop procedure should define both behaviors and be tested before deployment.

Consider open-order exposure

A common mistake is checking only filled inventory.

For example:

Current YES Inventory: 80
Open BUY Orders:       50

Potential Exposure:   130
Enter fullscreen mode Exit fullscreen mode

Even though the bot currently holds only 80 YES tokens, another 50 could still be filled.

Risk management must consider this potential exposure.


7. Monitor Your Bot in Production

A bot that is running is not necessarily a bot that is working correctly.

Imagine your VPS runs overnight.

At 03:00:

  • The WebSocket disconnects.
  • Market data stops updating.
  • An order remains open.
  • The strategy continues using old prices.

Without monitoring, you might not discover the problem until much later.

A production monitoring system should track four main categories.

Market Data

  • Last market update time
  • WebSocket connection status
  • Order book update frequency
  • Data validation failures

Execution

  • Submitted orders
  • Open orders
  • Canceled orders
  • Partial and full fills
  • Submission errors
  • Cancellation errors
  • Execution latency

Portfolio

  • Available balance
  • YES and NO inventory
  • Open-order exposure
  • Realized and unrealized P&L
  • Trading fees
  • Total portfolio equity

System

  • CPU and memory usage
  • Process uptime
  • Restart count
  • API response time
  • Database errors
  • Application exceptions

Use structured logging so you can search by order ID, market ID, token ID, and error type.

Never put private keys, passwords, or API secrets into application logs.


8. Build a Production Dashboard

A dashboard helps you understand your bot's condition without reading every log entry.

For example:

====================================
       POLYMARKET MARKET MAKER
====================================

BOT STATUS
Status: RUNNING
Mode: PAPER
Uptime: 12h 34m

MARKET DATA
WebSocket: CONNECTED
Last Update: 0.2s ago
Market: BTC YES/NO

ORDERS
Open Orders: 4
Submitted Today: 25
Partial Fills: 6
Completed Fills: 12

PORTFOLIO
YES Inventory: 80
NO Inventory: 60
Available Balance: $85.00

PERFORMANCE
Realized P&L: $2.50
Unrealized P&L: $0.80
Trading Costs: $0.35

RISK
Maximum Inventory: NORMAL
Daily Loss Limit: NORMAL
Market Data: FRESH

====================================
Enter fullscreen mode Exit fullscreen mode

The dashboard should clearly distinguish paper-trading results from actual live account values.

It should also show when data is unavailable or stale instead of displaying old values as if they were current.


9. Preserve Trading State with a Database

What happens if your VPS restarts unexpectedly?

If your bot stores everything only in Python variables, it may lose important information about previous orders and inventory.

A database helps preserve the system's state.

You may need tables such as:

Table Responsibility
bot_orders Order IDs, prices, quantities, and statuses
bot_trades Individual executions
inventory Current YES and NO positions
market_snapshots Historical market data
bot_events Important operational events
risk_events Risk violations and emergency stops

A simplified SQL example:

CREATE TABLE bot_orders (
    id BIGSERIAL PRIMARY KEY,
    exchange_order_id TEXT UNIQUE,
    market_id TEXT NOT NULL,
    token_id TEXT NOT NULL,
    side TEXT NOT NULL,
    price NUMERIC(18, 8) NOT NULL,
    original_quantity NUMERIC(18, 8) NOT NULL,
    filled_quantity NUMERIC(18, 8) NOT NULL DEFAULT 0,
    status TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Enter fullscreen mode Exit fullscreen mode

For execution records:

CREATE TABLE bot_trades (
    id BIGSERIAL PRIMARY KEY,
    exchange_trade_id TEXT UNIQUE,
    exchange_order_id TEXT,
    token_id TEXT NOT NULL,
    side TEXT NOT NULL,
    price NUMERIC(18, 8) NOT NULL,
    quantity NUMERIC(18, 8) NOT NULL,
    executed_at TIMESTAMPTZ NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

The unique execution identifier helps prevent duplicate fills from being recorded twice.

For financial calculations, use exact decimal arithmetic rather than ordinary floating-point calculations.

Important: Your local database should not automatically be treated as the source of truth for current exchange state.

After restarting, reconcile your stored records against the exchange's open orders, recent trades, and account balances.


10. Recover Safely After a Restart

Restarting a trading bot requires more than simply running the Python application again.

Imagine:

  • Three orders were open.
  • Two orders had partial fills.
  • The VPS restarted.
  • The Python process started again.

The bot must understand what happened before it starts trading.

A recommended startup sequence:

          Start Bot
              |
              v
       Load Configuration
              |
              v
       Connect to Exchange
              |
              v
        Retrieve Balances
              |
              v
       Retrieve Open Orders
              |
              v
       Retrieve Recent Trades
              |
              v
      Reconcile Local Database
              |
              v
       Validate Market Data
              |
              v
          Run Risk Checks
              |
              v
       Enable New Quoting
Enter fullscreen mode Exit fullscreen mode

If the exchange state and local database disagree, stop new trading and investigate.

For example, your database says an order is canceled, but the exchange reports that it is still open.

You must reconcile that order before continuing.


11. Deploy the Bot on a VPS

A VPS (Virtual Private Server) allows your bot to run continuously without keeping your personal computer switched on.

A basic deployment architecture:

Developer Computer
        |
        v
      Git Push
        |
        v
   Git Repository
        |
        v
       VPS
        |
   +----+----+----+
   |         |    |
   v         v    v
 Python   Database Monitoring
Environment
   |
   v
Market Maker Bot
Enter fullscreen mode Exit fullscreen mode

Basic deployment steps

Step 1 — Prepare the server

Install Python and the required system dependencies.

Step 2 — Clone the repository

git clone YOUR_REPOSITORY_URL
cd polymarket-market-maker
Enter fullscreen mode Exit fullscreen mode

Step 3 — Create a virtual environment

python3 -m venv .venv
source .venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Step 4 — Install dependencies

pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Step 5 — Configure environment variables

Store authentication credentials securely.

Never commit real credentials to GitHub.

Step 6 — Start in paper mode

python -m src.main --mode paper
Enter fullscreen mode Exit fullscreen mode

This assumes your application implements a --mode argument.

Step 7 — Check logs

Verify that:

  • Market data is updating.
  • Strategy calculations are working.
  • Simulated orders are recorded.
  • Inventory calculations are correct.
  • Risk checks are active.
  • No unexpected errors appear.

Step 8 — Configure process supervision

Use an appropriate process manager to monitor the application and control restarts.

However, automatic restarts do not guarantee trading safety.

Your application must complete its recovery and reconciliation process before enabling new orders.


12. Protect API Credentials

Security is part of production engineering.

Your bot may need credentials or signing permissions to submit orders.

Important practices:

  • Never publish private keys on GitHub.
  • Never hardcode secrets in Python files.
  • Use environment variables or a secure secret manager.
  • Restrict server access.
  • Prefer SSH keys over password-only access.
  • Keep dependencies updated.
  • Separate testing credentials from production credentials.
  • Rotate credentials if they may have been exposed.
  • Never print authentication headers or private keys in logs.

Evaluate restricted signing permissions or session-based authorization when supported by the current official documentation.

The goal is to reduce potential damage if your server or application is compromised.


13. Follow a Gradual Live-Trading Plan

Don't move directly from backtesting to large live orders.

Use a controlled rollout.

Phase Main objective
1. Backtesting Validate strategy and historical performance
2. Paper Trading Test against current market data
3. Operational Testing Simulate failures and recovery
4. Small Live Deployment Test real execution with strict limits
5. Gradual Evaluation Evaluate actual performance before increasing exposure

During operational testing, deliberately simulate:

  • WebSocket disconnections.
  • VPS restarts.
  • API timeouts.
  • Invalid market data.
  • Risk-limit violations.
  • Partially filled orders.

Confirm that the bot behaves safely in every scenario.

There is no fixed number of paper-trading hours that proves a strategy is safe. The testing period should depend on market activity and the amount of execution data collected.


14. Production Readiness Checklist

Before enabling real orders, review the following checklist.

Strategy

  • [ ] Bid and ask calculations are tested.
  • [ ] Dynamic spread logic is tested.
  • [ ] Inventory adjustments are working.
  • [ ] Stale market data prevents new quotes.

Execution

  • [ ] Order submission is implemented.
  • [ ] Order cancellation is implemented.
  • [ ] Partial fills are handled.
  • [ ] Duplicate execution events are prevented.
  • [ ] Unknown submission states are reconciled.
  • [ ] Existing orders are checked before replacement.

Portfolio

  • [ ] YES inventory is tracked.
  • [ ] NO inventory is tracked.
  • [ ] Available balances are checked.
  • [ ] Open-order exposure is included.
  • [ ] Realized and unrealized P&L are separated.
  • [ ] Fees and resolution outcomes are accounted for.

Risk

  • [ ] Maximum order size is configured.
  • [ ] Maximum inventory is configured.
  • [ ] Capital allocation is limited.
  • [ ] Daily loss limits are configured.
  • [ ] Emergency-stop behavior is tested.
  • [ ] Strategy code cannot bypass risk checks.

Infrastructure

  • [ ] Logs are available.
  • [ ] Monitoring is configured.
  • [ ] Database records are persistent.
  • [ ] Startup reconciliation is implemented.
  • [ ] Credentials are protected.
  • [ ] VPS access is secured.
  • [ ] Backups and recovery procedures are tested.

Conclusion

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

A production trading system must manage the entire process:

  1. Collect reliable market data.
  2. Generate trading quotes.
  3. Validate risk limits.
  4. Submit and manage orders.
  5. Track partial fills.
  6. Manage inventory.
  7. Recover from failures.
  8. Monitor performance.
  9. Preserve state after restarts.

The most important lesson is:

Never assume an order was filled, canceled, or rejected until you have enough information to confirm its actual state.

Backtesting helps evaluate your strategy.

Paper trading helps test the system under current market conditions.

Production engineering makes the system more reliable.

However, none of these guarantees profitable trading. Real execution introduces financial risks that simulations cannot completely reproduce.

Before increasing trading activity, focus on making your bot predictable, observable, and recoverable.


Continue Building Your Polymarket Trading Bot

If you're following this development series, you can explore the project and tutorials here:

Coming Next — Day 11

Advanced Polymarket Market Maker Architecture: WebSockets, Real-Time Order Book Processing, and Low-Latency Execution

In the next article, we'll explore:

  • WebSocket market data connections.
  • Processing real-time order book updates.
  • Maintaining local order book state.
  • Detecting stale or missing data.
  • Asynchronous Python architecture.
  • Separating market data and execution tasks.
  • Reducing unnecessary API requests.
  • Building a more responsive trading system.

The goal is to understand how to build a market maker that reacts to changing market conditions without relying on slow, repeated polling.

polymarket #trading #bot #tutorial #python

Top comments (0)