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.
Consider this situation:
- Your bot submits a BUY order for 100 YES tokens.
- The exchange fills 40 tokens.
- The remaining 60 tokens are still open.
- The market price suddenly changes.
- Your bot requests cancellation.
- 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 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 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 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): ... Different execution implementations can follow the same interface:
ExecutionClient | +---------+---------+ | | | v v v Backtest Paper Live Client Client Client 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 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 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 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 When a submission response is uncertain:
- Do not immediately submit another identical order.
- Check the exchange's current order state.
- Search using available order identifiers.
- Reconcile open orders and recent trades.
- 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) 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 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 ) 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 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 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 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 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 ==================================== 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() ); 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 ); 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 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 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 Step 3 — Create a virtual environment
python3 -m venv .venv source .venv/bin/activate Step 4 — Install dependencies
pip install -r requirements.txt 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 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:
- Collect reliable market data.
- Generate trading quotes.
- Validate risk limits.
- Submit and manage orders.
- Track partial fills.
- Manage inventory.
- Recover from failures.
- Monitor performance.
- 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:
- GitHub: https://github.com/Benj4minCup/Polymarket-Trading-Bot-python
- YouTube: https://www.youtube.com/watch?v=UCF7MA1Kekk
- Telegram: https://telegram.me/BenjaminCup
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.
