It was a Wednesday afternoon in February 2025. BTC dropped 0.4% in 60 seconds on Binance. On Polymarket’s 5-minute BTC UP/DOWN market, the YES price sat at 0.55. It should have been closer to 0.30. A bot running on a server in Amsterdam saw the Binance WebSocket update, calculated the mispricing, and submitted a NO order within 80ms. The fill was clean. By the time a manual trader noticed the same discrepancy, the orderbook had already moved.
That 30-to-90-second lag between Binance and Polymarket is a documented edge. One developer reported a 69.6% win rate across 23 clean trades in four days using exactly that signal. Another bot, running weather forecasts against temperature markets on Polymarket, generated $1,800 in profits. An AI-driven bot called “ilovecircle” made $2.2 million in two months with a 74% win rate.
None of those results came from a laptop running on home Wi-Fi. They came from automated systems running 24 hours a day, seven days a week, on dedicated servers close to the exchange. This guide covers the full setup: choosing the right Polymarket bot VPS, installing dependencies, authenticating your wallet, writing strategy logic, handling rate limits, and keeping your bot running without interruption.
Python version 3.9 or above is required. You need a Polygon wallet, USDC for capital, and a small amount of POL for gas fees.
Understanding Polymarket’s Architecture Before You Write a Line of Code
Bots fail most often at the authentication layer and the API layer, not the strategy layer. Knowing how Polymarket’s infrastructure works saves hours of debugging.
The Three APIs and What Each One Does
Polymarket splits its functionality across three separate APIs. Each one serves a distinct purpose, and you will use all three in a complete bot.
The CLOB API at https://clob.polymarket.com is the trading engine. It handles order placement, order cancellation, live orderbook data, and fill confirmations. Every trade goes through this endpoint. The CLOB runs off-chain for speed, then settles on-chain via the Polygon blockchain. Order matching happens in single-digit milliseconds at the server level. Your network latency determines how fast your orders arrive after you send them.
The Gamma API at https://gamma-api.polymarket.com is the discovery layer. It lists available markets, event metadata, outcome descriptions, and token IDs. You use this API to find which markets are open and which token IDs correspond to YES and NO sides. Data here has roughly a 1-second indexing lag, so it is not suitable for real-time pricing decisions.
The Data API at https://data-api.polymarket.com manages account-level information. Positions, trade history, and P&L live here. Use this for monitoring your bot’s performance, not for execution logic.
| API | Endpoint | Primary Use | Latency Sensitivity |
|---|---|---|---|
| CLOB API | clob.polymarket.com | Order placement, cancellation, orderbook | High: every millisecond counts |
| Gamma API | gamma-api.polymarket.com | Market discovery, token IDs, event data | Low: poll every 60 seconds |
| Data API | data-api.polymarket.com | Positions, trade history, P&L | Low: monitoring only |
| CLOB WebSocket | ws-subscriptions-clob.polymarket.com/ws/ | Live orderbook deltas, fill notifications | Critical: use for all real-time data |
| RTDS WebSocket | ws-live-data.polymarket.com | Low-latency crypto price feeds (100ms delay) | High: for crypto signal bots |
How the CLOB Matches Orders
Polymarket’s CLOB works like a traditional limit order book. Makers post limit orders at specific prices. Takers fill against those resting orders. Prices on each market range from $0.01 to $0.99, representing probability. A YES contract at $0.65 implies a 65% market-implied probability of the event occurring. If it resolves YES, that contract pays $1.00. If it resolves NO, it pays $0.00.
Fee structure as of March 2026: makers pay 0% on all markets. Taker fees vary by category. Crypto markets run 1.80% at peak (50% probability), sports 0.75%, finance and politics 1.00%. Geopolitics markets are fee-free. Maker rebates exist and vary by market. Makers in high-volume markets can earn back a portion of taker fees paid by others.
For bots, the implication is clear. Limit orders cost nothing and earn rebates. Market orders cost up to 1.80% per trade on crypto markets. A pure market-order bot in crypto prediction markets needs a reliable edge of more than 1.80% per trade just to break even on fees alone before accounting for slippage.
Where Polymarket’s Servers Actually Sit
Polymarket’s CLOB infrastructure runs on AWS in the eu-west-2 region, which is London. The CLOB WebSocket server sits at the same AWS region. From a US East Coast connection, round-trip latency to the CLOB averages around 130ms. From Dublin, it drops under 5ms. From Amsterdam, it runs around 10ms.
This matters for time-sensitive strategies. A bot running BTC 5-minute markets and detecting a Binance signal needs to submit an order before Polymarket’s market makers reprice. At 130ms round-trip, that bot is 120ms behind any European competitor. On a 30-to-90 second edge window, that is survivable but not ideal. On a 5-second edge window, like an esports event bot reading live game API data, 130ms is a real problem.
For Polymarket-specific bots, a European VPS is the correct location choice.
TradoxVPS launched a Polymarket VPS in Dublin, Ireland delivering under 0.5ms latency to Polymarket’s CLOB API endpoints. Dublin sits one network hop from Polymarket’s AWS eu-west-2 region in London, is not on Polymarket’s geoblocking list, and benefits from Ireland’s Tier-1 peering infrastructure. That combination puts your bot at the front of the orderbook queue, not 130ms behind every European competitor.
The TradoxVPS Dublin VPS runs the same AMD Ryzen 9 9950X hardware, DDR5 RAM, and NVMe storage as the Chicago fleet, with 99.999% uptime and Path.net DDoS protection built in.
Choosing and Provisioning Your VPS
The server is not background infrastructure. It is an active component of your strategy’s execution quality. Treating it as a commodity decision costs money on every trade.
What Hardware Your Bot Actually Needs
Polymarket bots are not compute-heavy in the traditional sense. They are network-bound and latency-bound. The workload breaks into three parts: reading WebSocket data, running signal logic, and submitting orders via REST. Each of these runs on a single thread. Multi-core counts matter less than clock speed per core.
A bot monitoring 10 to 20 markets via WebSocket, running a fair-value model, and submitting orders 10 to 30 times per minute needs roughly 2 CPU cores and 4GB RAM. Add a logging system writing to disk continuously and you want NVMe storage, not SATA SSD, to keep log writes from blocking the critical path. For multi-bot setups, or for bots that also run NinjaTrader or another platform on the same VPS, scale up to 4 to 6 cores and 16GB RAM.
| Bot Type | Recommended Cores | RAM | Storage | TradoxVPS Plan | Monthly Cost |
|---|---|---|---|---|---|
| Single-market arb scanner | 2 | 8GB DDR5 | 150GB NVMe | Starter Trader | $39 |
| Multi-market market maker (10–20 markets) | 4 | 16GB DDR5 | 300GB NVMe | Active Trader | $69 |
| Cross-platform arb (Polymarket + Kalshi) | 6 | 24GB DDR5 | 400GB NVMe | Advanced Trader | $99 |
| High-frequency multi-bot environment | 8 | 32GB DDR5 | 500GB NVMe | High Performance | $129 |
| Full quant operation, bots plus futures | 12 | 48GB DDR5 | 700GB NVMe | Ultra Low Latency | $179 |
Operating System: Linux vs Windows
Both work. Linux gives you systemd for process management, which is the cleanest way to keep a bot running 24/7 with auto-restart on crashes. Ubuntu 22.04 or 24.04 are the standard choices. All py-clob-client dependencies install cleanly on both.
Windows Server 2022, the default OS on TradoxVPS, handles Python bots without issues. You use Task Scheduler instead of systemd, or run the bot in a PowerShell window inside a screen session. If you run NinjaTrader, TradeStation, or another Windows-native platform on the same VPS alongside your Polymarket bot, Windows makes that easier.
Connecting to Your VPS
On Linux, connect via SSH from your terminal. On Windows Server, connect via Remote Desktop Protocol (RDP). Both give you full administrator access. Your first session should update the system, set the timezone to UTC, and configure a firewall to block all inbound traffic except your own IP and SSH/RDP ports. A bot that connects to Polymarket’s API does not need any inbound ports open at all.
# First commands after SSH connection to Linux VPS sudo apt update && sudo apt upgrade -y sudo timedatectl set-timezone UTCSetting the VPS timezone to UTC matters. Polymarket’s API timestamps are UTC. A mismatch between your server clock and the API server causes authentication errors. Requests expire after 30 seconds, and a clock drift of more than that will fail every authenticated call.
Setting Up Python and py-clob-client
The Python setup is straightforward but has specific version and package pinning requirements. Getting these wrong causes subtle bugs: orders execute incorrectly but still return HTTP 200 success codes.
Installing Python 3.9 or Above
Polymarket’s official Python SDK, py-clob-client, requires Python 3.9 or above. On Ubuntu 22.04, Python 3.10 ships by default. On Ubuntu 20.04 or older, install 3.10 from the deadsnakes PPA.
# Ubuntu 22.04 — Python 3.10 already installed
python3 --version
# Python 3.10.x
# Create a virtual environment for the bot
python3 -m venv polybot-env
source polybot-env/bin/activate
# Install core dependencies
pip install py-clob-client
pip install web3==6.14.0
pip install python-dotenv
pip install websockets
Pin web3 to version 6.14.0. The py-clob-client SDK uses specific web3 internals that break on newer versions without warning. A newer web3 install looks fine during setup but throws runtime errors when the bot tries to sign its first order. The error messages are not helpful. They point to signing failures rather than the version mismatch. Pin the version and avoid the problem entirely.
Setting Up Environment Variables
Never hardcode private keys or API credentials in your bot’s source code. Store all secrets in a .env file and load them at runtime. If you ever commit the bot’s code to a Git repository with a hardcoded key, that key is compromised the moment it touches GitHub, even if you delete it immediately.
# .env file — keep this file out of version control
PRIVATE_KEY=your_polygon_wallet_private_key_here
POLYMARKET_API_KEY=your_api_key
POLYMARKET_API_SECRET=your_api_secret
POLYMARKET_PASSPHRASE=your_api_passphrase
WALLET_ADDRESS=your_polygon_wallet_address
CHAIN_ID=137
Add .env to your .gitignore file immediately. Set file permissions on the VPS so only your user can read the .env file:
chmod 600 .env
Load the environment variables in your bot script using python-dotenv:
import os
from dotenv import load_dotenv
load_dotenv()
PRIVATE_KEY = os.getenv("PRIVATE_KEY")
API_KEY = os.getenv("POLYMARKET_API_KEY")
API_SECRET = os.getenv("POLYMARKET_API_SECRET")
PASSPHRASE = os.getenv("POLYMARKET_PASSPHRASE")
WALLET = os.getenv("WALLET_ADDRESS")
Authenticating Your Wallet and Setting Token Allowances
Authentication is the most common point of failure for first-time Polymarket bot builders. The process has two distinct layers, and both must work before any order can be placed.
The Two Authentication Layers
Level 1 (L1) authentication uses your Polygon wallet private key to prove ownership. This gives read access and the ability to generate API credentials. Level 2 (L2) authentication uses HMAC-SHA256 signed request headers: the API key, secret, and passphrase generated from your L1 wallet signature. Trading operations require both layers active simultaneously.
The py-clob-client SDK handles both layers automatically once you initialize the client with the correct parameters. The critical parameter to get right is signature_type:
- signature_type=0: Standard EOA wallet (MetaMask, Rabby, hardware wallet)
- signature_type=1: Polymarket email or Magic wallet
- signature_type=2: Gnosis Safe or browser proxy wallet
Mismatching this parameter does not always throw a clear error. It can produce silently incorrect order signing that the API accepts as valid but fills at the wrong account. Test authentication in simulation mode first.
from py_clob_client.client import ClobClient
HOST = "https://clob.polymarket.com"
CHAIN_ID = 137 # Polygon mainnet
# For standard EOA wallet (MetaMask)
client = ClobClient(
HOST,
key=PRIVATE_KEY,
chain_id=CHAIN_ID,
signature_type=0
)
# Generate or derive API credentials from your wallet
api_creds = client.create_or_derive_api_creds()
client.set_api_creds(api_creds)
# Verify the connection works
print(client.get_ok()) # Should return "OK"
print(client.get_server_time()) # Should return current UTC time
Setting Token Allowances
Before your first order, the Polymarket smart contracts need authorization to move USDC and Conditional Tokens on your behalf. This is called setting token allowances. You do it once per wallet. You do not repeat this for every bot restart.
Polymarket provides an official allowance script in their GitHub repository. Run it once:
# Install web3 if not already installed
pip install "web3==6.14.0"
# Set required environment variables first
export POLYGON_PRIVATE_KEY=your_private_key
export POLYGON_PUBLIC_KEY=your_wallet_address
# Then run the official Polymarket allowance setup script
# Available at: https://github.com/Polymarket/py-clob-client
python set_allowances.py
The allowance setup approves the maximum USDC amount for the Polymarket USDC contract, the Conditional Token Framework (CTF) contract, and the CLOB Exchange contract. You need a small amount of POL in your wallet to pay Polygon gas fees for these approval transactions. Around 1 to 2 POL covers it. Keep 5 to 10 POL as a running balance for ongoing gas costs.
Once allowances are set, the connection is ready. The next step is writing actual strategy logic.
Writing Your Bot’s Strategy Logic
Strategy design is where most guides stop being useful. They show code snippets that fetch market data and place orders, but skip the logic that decides whether to place an order and for how much. That gap is where real money is lost.
Reading the Orderbook and Getting Live Prices
Use the WebSocket endpoint for real-time price data, not REST polling. REST polling introduces a lag equal to your polling interval. At 1-second polling, your bot is always operating on data that is up to 1 second stale. The WebSocket pushes orderbook deltas the moment they happen.
import asyncio
import websockets
import json
async def stream_orderbook(token_id: str):
uri = "wss://ws-subscriptions-clob.polymarket.com/ws/"
async with websockets.connect(uri) as ws:
# Subscribe to the market's orderbook channel
await ws.send(json.dumps({
"type": "subscribe",
"channel": "market",
"assets_id": token_id
}))
async for message in ws:
data = json.loads(message)
# Process orderbook delta
process_update(data)
def process_update(data):
# Extract best bid and ask from the delta
# Run your signal logic here
pass
For market discovery, finding which markets are active and getting their token IDs, use the Gamma API:
import requests
def get_active_markets(category: str = "crypto", limit: int = 50):
url = "https://gamma-api.polymarket.com/markets"
params = {
"active": True,
"category": category,
"limit": limit
}
resp = requests.get(url, params=params)
markets = resp.json()
return markets
markets = get_active_markets()
for m in markets:
print(m['question'], m['conditionId'], m['tokens'])
Calculating Position Size with the Kelly Criterion
Position sizing is a risk management decision, not a strategy decision. The Kelly Criterion gives the mathematically correct fraction of your bankroll to bet when you have a known edge. Full Kelly is aggressive. Most production bots use fractional Kelly, between 0.15 and 0.25 of the full Kelly output, to reduce variance and account for model uncertainty.
def kelly_criterion(true_prob: float, market_price: float,
bankroll: float, fraction: float = 0.25) -> float:
"""
Calculate optimal position size using fractional Kelly.
true_prob: your model's estimated probability (0.0 to 1.0)
market_price: current market price of the contract (0.0 to 1.0)
bankroll: total capital available in USDC
fraction: Kelly fraction (0.25 = quarter Kelly)
"""
if market_price <= 0 or market_price >= 1:
return 0.0
# b = decimal odds on the bet
b = (1.0 / market_price) - 1.0
p = true_prob
q = 1.0 - p
full_kelly = (b * p - q) / b
if full_kelly <= 0:
return 0.0 # No edge — skip this trade
# Apply fractional Kelly and cap at 10% of bankroll per trade
position = fraction * full_kelly * bankroll
max_position = bankroll * 0.10
return min(position, max_position)
# Example
my_prob = 0.72 # Your model says 72% chance of YES
market_price = 0.58 # Market currently showing 58% (YES at 0.58)
bankroll = 1000.0 # $1,000 USDC
size = kelly_criterion(my_prob, market_price, bankroll)
print(f"Position size: ${size:.2f}")
# Edge = 14¢ per $1. Fractional Kelly at 0.25 on $1,000 = $35.00
The formula outputs $0 when your model’s probability is below the market price. The bot does not trade when there is no edge. That is the entire point. A bot that passes on 90% of markets and only trades when the Kelly output is positive will outperform one that forces trades on marginal signals.
Placing Orders
Two order types matter for bots: GTC (Good Till Cancelled) limit orders and FOK (Fill or Kill) market orders. GTC orders sit on the orderbook as maker orders and pay zero fees. FOK orders fill immediately at market price or cancel. They pay taker fees. Use GTC for market making and liquidity provision. Use FOK for arbitrage execution where you need a specific fill right now and cannot wait for a maker fill.
from py_clob_client.clob_types import OrderArgs, MarketOrderArgs, OrderType
from py_clob_client.order_builder.constants import BUY, SELL
def place_limit_order(client, token_id: str, price: float,
size: float, side: str) -> dict:
"""
Place a GTC limit order (maker order, zero fees).
token_id: the YES or NO token ID from Gamma API
price: limit price between 0.01 and 0.99
size: number of contracts (dollars at payout)
side: BUY or SELL
"""
order_args = OrderArgs(
token_id=token_id,
price=price,
size=size,
side=side
)
signed_order = client.create_order(order_args)
result = client.post_order(signed_order, OrderType.GTC)
return result
def place_market_order(client, token_id: str,
amount: float, side: str) -> dict:
"""
Place a FOK market order (taker order, fees apply).
amount: dollar amount to spend
"""
order_args = MarketOrderArgs(
token_id=token_id,
amount=amount,
side=side,
order_type=OrderType.FOK
)
signed_order = client.create_market_order(order_args)
result = client.post_order(signed_order, OrderType.FOK)
return result
One common mistake: trying to place FOK orders in thin markets. FOK cancels if it cannot fill the entire order immediately. A $500 FOK order in a market with only $200 of depth at your price cancels completely. Use FAK (Fill and Kill) for partial fills in thin markets, or break large orders into smaller GTC limit orders.
Handling Rate Limits, Errors, and Reconnections
A bot that crashes on the first API error is not a production bot. Error handling is not optional. It is the difference between a system that runs for weeks and one that breaks at 3 AM during the Fed announcement you were trying to trade.
Rate Limits You Must Respect
Polymarket’s CLOB API enforces 60 orders per minute per API key. REST market data endpoints allow around 10 requests per second. Hitting these limits returns HTTP 429 errors. The API uses throttling rather than hard rejection. Requests queue and slow down before being dropped entirely.
The solution is to shift real-time data consumption to WebSockets and batch REST calls where possible. A bot that polls the orderbook 10 times per second via REST will hit rate limits within minutes in a multi-market setup. The same bot using WebSocket subscriptions and polling REST only for account data every 30 seconds runs cleanly within limits for hundreds of simultaneous markets.
import time
from functools import wraps
def rate_limited(max_per_second: float):
"""Simple rate limiter decorator."""
min_interval = 1.0 / max_per_second
last_called = [0.0]
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
wait = min_interval - elapsed
if wait > 0:
time.sleep(wait)
result = func(*args, **kwargs)
last_called[0] = time.time()
return result
return wrapper
return decorator
@rate_limited(max_per_second=8) # Stay under 10/s limit with margin
def fetch_account_data(client):
return client.get_positions()
Retry Logic for Transient Errors
Network errors, connection resets, and temporary server-side failures all return 5xx status codes. Retry these with exponential backoff: start at 1 second, double on each retry, cap at 30 seconds, and give up after 5 attempts. Do not retry authentication errors (401) or bad request errors (400) without fixing the underlying cause first.
import time
import requests
def api_call_with_retry(func, max_retries: int = 5,
base_delay: float = 1.0):
"""Retry on 5xx errors with exponential backoff."""
for attempt in range(max_retries):
try:
result = func()
return result
except Exception as e:
error_str = str(e)
# Do not retry client errors
if "401" in error_str or "400" in error_str:
raise
if attempt == max_retries - 1:
raise
delay = base_delay * (2 ** attempt)
print(f"Attempt {attempt + 1} failed. Retrying in {delay}s")
time.sleep(delay)
WebSocket Reconnection
WebSocket connections drop. Network blips, server-side resets, and Polygon congestion all cause disconnects. A bot that does not reconnect automatically misses all orderbook updates until a human restarts it. For a market-making bot, a missed update means stale quotes sitting on the book, getting adversely filled by informed traders who know the price has moved.
import asyncio
import websockets
async def subscribe_with_reconnect(token_ids: list, handler):
"""WebSocket subscriber that reconnects on any disconnect."""
uri = "wss://ws-subscriptions-clob.polymarket.com/ws/"
while True: # Outer loop for reconnection
try:
async with websockets.connect(uri) as ws:
for token_id in token_ids:
await ws.send(json.dumps({
"type": "subscribe",
"channel": "market",
"assets_id": token_id
}))
async for message in ws:
await handler(json.loads(message))
except (websockets.exceptions.ConnectionClosed,
OSError, asyncio.TimeoutError) as e:
print(f"WebSocket disconnected: {e}. Reconnecting in 3s.")
await asyncio.sleep(3)
Three Bot Strategies and Their Infrastructure Requirements
Not every Polymarket strategy has the same latency sensitivity or compute requirement. The strategy you choose determines the server you need and the region it should sit in.
Strategy 1: Intra-Market Arbitrage (YES + NO under $1.00)
When the combined price of YES and NO contracts on the same market totals less than $1.00, guaranteed profit exists for whoever fills both sides before the gap closes. A YES at $0.44 and a NO at $0.53 costs $0.97 combined. One of them will pay $1.00. You locked in $0.03 per contract, minus fees.
These windows close in seconds in liquid markets. In December 2025, a bot called “gabagool” bought 1,266 YES shares at $0.517 and 1,294 NO shares simultaneously, locking in gross profit before the spread closed. From April 2024 to April 2025, documented intra-market arbitrage profits across Polymarket totalled over $40 million, with the top three wallets generating $4.2 million between them.
This strategy needs the lowest latency of all three. FOK orders on both sides must arrive and fill within the same market state. Server proximity to the CLOB matters most here.
Strategy 2: Cross-Signal Market Trading (Binance BTC Lag)
Polymarket’s 5-minute BTC UP/DOWN markets reprice in response to Binance spot moves. Market makers on Polymarket are not colocated at Binance. Their observation and repricing cycle creates a 30-to-90 second window after a large BTC move during which the Polymarket odds have not fully adjusted.
The signal: monitor Binance’s WebSocket price feed. When BTC moves more than 0.3% in 60 seconds, check the corresponding Polymarket YES price for the 5-minute UP market. If it has not moved proportionally, place a position before market makers reprice. The edge window is 30 to 90 seconds, wide enough that 130ms latency from a US server is not disqualifying, but tighter server placement in Europe improves fill quality.
One developer documented a 69.6% win rate across 23 clean trades using exactly this approach in February 2025, generating net profit of $11.51 on a small starting balance. The strategy scales with capital. The same signal structure and edge window holds at $100 or $10,000.
Strategy 3: Market Making (Continuous Liquidity Provision)
A market-making bot quotes both YES and NO sides on active markets, earning the spread between its bid and ask. It adjusts quotes continuously as the orderbook shifts. When it fills on the YES side, it cancels or reduces its YES offer and posts more on the NO side to balance inventory.
This strategy pays zero maker fees and earns rebates. It requires high uptime. A bot that goes offline leaves stale quotes on the book that informed traders fill adversely. It also requires fast order cancellation. When an event moves the market sharply, the bot must cancel stale quotes before they get picked off.
Market making is the most infrastructure-intensive of the three. It needs continuous WebSocket connection, fast REST response for cancellations, and 99.999% server uptime. A single server reboot during a volatile event window can produce significant adverse fills on unmanaged resting orders.
| Strategy | Latency Sensitivity | Uptime Requirement | Order Type | Fee Impact | Minimum Capital |
|---|---|---|---|---|---|
| Intra-market arbitrage | Critical: sub-second | High | FOK taker | Up to 1.80% on crypto | $200+ |
| Cross-signal (Binance lag) | High: sub-10 seconds | High | Limit or FOK | Low if using GTC | $100+ |
| Market making | High: continuous quotes | Critical: 99.999% | GTC maker | Zero + rebates | $500+ |
Deploying the Bot for 24/7 Operation
A bot that runs on your laptop is a prototype. A bot running on a VPS under a process manager with auto-restart and logging is a production system. The setup takes 20 minutes and saves hours of manual intervention every week.
Transferring Files to the VPS
Use SCP to copy your bot files from your local machine to the VPS:
# From your local machine — copy the entire project folder
scp -r ./polybot user@your-vps-ip:/home/user/polybot
# Or use rsync for incremental updates
rsync -avz --exclude '.env' ./polybot/ user@your-vps-ip:/home/user/polybot/
Note the --exclude '.env' in the rsync command. Create a fresh .env file directly on the VPS instead of copying it from your local machine. This keeps your secrets out of any sync tool’s logs.
Setting Up systemd for Auto-Restart on Linux
A systemd service starts your bot at VPS boot, restarts it automatically if it crashes, and logs output to the system journal. Create the service file:
# /etc/systemd/system/polybot.service
[Unit]
Description=Polymarket Trading Bot
After=network.target
[Service]
Type=simple
User=your-username
WorkingDirectory=/home/your-username/polybot
EnvironmentFile=/home/your-username/polybot/.env
ExecStart=/home/your-username/polybot-env/bin/python main.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable polybot
sudo systemctl start polybot
# Check status
sudo systemctl status polybot
# View live logs
sudo journalctl -u polybot -f
The Restart=always directive restarts the bot 5 seconds after any crash. The EnvironmentFile directive loads your .env secrets without putting them in the service file itself. The bot survives VPS reboots automatically.
Logging and Monitoring
Log every significant event with a UTC timestamp: WebSocket connection status, signal events, order submissions, fill confirmations, cancellations, and errors. Write logs to a rotating file, not just stdout, so you can review what happened during any time window after the fact.
import logging
import logging.handlers
from datetime import datetime, timezone
def setup_logging():
logger = logging.getLogger("polybot")
logger.setLevel(logging.INFO)
# Rotate logs at 50MB, keep last 10 files
handler = logging.handlers.RotatingFileHandler(
"logs/polybot.log",
maxBytes=50 * 1024 * 1024,
backupCount=10
)
formatter = logging.Formatter(
"%(asctime)s UTC | %(levelname)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
formatter.converter = time.gmtime # Force UTC timestamps
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
logger = setup_logging()
# Example log entries
logger.info(f"Order placed: token={token_id} side=YES size={size} price={price}")
logger.info(f"Fill received: order_id={order_id} filled={filled_size}")
logger.error(f"WebSocket disconnected: {error}")
NVMe storage on the VPS handles rotating log writes without any measurable impact on bot latency. On a SATA SSD or spinning disk, heavy logging can block the Python thread during a flush. This is a real cause of missed fills in high-frequency setups.
Setting a Circuit Breaker
A bot running with real money needs a hard stop. A circuit breaker shuts the bot down when losses exceed a pre-defined daily threshold. This is not optional for automated systems. Strategies can break in production in ways that backtesting never predicted.
class CircuitBreaker:
def __init__(self, max_daily_loss_usdc: float):
self.max_loss = max_daily_loss_usdc
self.daily_loss = 0.0
self.triggered = False
def record_loss(self, amount: float):
self.daily_loss += amount
if self.daily_loss >= self.max_loss:
self.triggered = True
logger.critical(
f"Circuit breaker triggered. "
f"Daily loss: ${self.daily_loss:.2f}. Bot stopping."
)
def is_ok(self) -> bool:
return not self.triggered
# Initialize with a $100 daily loss limit
breaker = CircuitBreaker(max_daily_loss_usdc=100.0)
# In your main trading loop:
if not breaker.is_ok():
# Cancel all open orders before shutting down
client.cancel_all()
break
Security Hardening for a Live Trading Bot
A Polymarket bot connects to the Polygon blockchain with a private key that controls real USDC. The attack surface is narrow but the consequences of a breach are immediate and irreversible. Five minutes of security hardening per deployment prevents the scenario where someone drains your trading wallet.
Firewall and SSH Hardening
On Linux, configure UFW to deny all inbound traffic except SSH from your own IP address. Your bot does not need to receive any inbound connections. All Polymarket API calls are outbound.
# Allow SSH only from your IP sudo ufw allow from YOUR.IP.ADDRESS to any port 22 sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw enable # Disable password SSH login — use key-based auth only # Edit /etc/ssh/sshd_config and set: # PasswordAuthentication no # PermitRootLogin no sudo systemctl restart sshd
Wallet Spending Limits
Fund the trading wallet with only the capital your strategy needs for the current week. Keep the bulk of your USDC in a separate cold wallet. The trading wallet should hold no more than your weekly risk budget. If the private key is ever compromised, the attacker can only drain what is in the trading wallet, not your entire holdings.
Set a maximum position size in your bot code, both as a percentage of bankroll and as a hard dollar cap per trade. The Kelly Criterion will calculate large position sizes on strong signals. Cap it at 10% of bankroll regardless of what the formula outputs, until the strategy is proven over at least 200 live trades.
Environment Variable Security on the VPS
Set file permissions on your .env file so only the service account that runs the bot can read it:
chmod 600 /home/your-username/polybot/.env chown your-username:your-username /home/your-username/polybot/.env
Do not store the private key in the systemd unit file’s Environment= directive. That file is world-readable by default via systemctl show. Use EnvironmentFile= pointing to the 600-permissions .env file instead. That path loads the secrets without exposing them to the systemd inspection tools.
Testing Before Going Live
Every bot should run in simulation mode on real market data before touching real USDC. Running paper trades against live prices catches most strategy bugs and code errors without financial cost.
Simulation Mode Implementation
Add a DRY_RUN flag to your .env file and check it before every order submission. In dry run mode, the bot reads real market data, calculates real signals, and logs what it would do, but does not submit orders.
DRY_RUN = os.getenv("DRY_RUN", "true").lower() == "true"
def submit_order(client, order_args, order_type):
if DRY_RUN:
logger.info(
f"[DRY RUN] Would submit: {order_args.side} "
f"{order_args.size} @ {order_args.price} "
f"on token {order_args.token_id}"
)
return {"status": "simulated"}
return client.post_order(
client.create_order(order_args), order_type
)
Run the bot in dry run for at least 48 hours across different market conditions. Check the logs for signal frequency, position sizing outputs, and error patterns. A bot that logs 500 simulated trades in 48 hours with consistent logic is ready for a small live test. One that logs 3 simulated trades because the signal condition is almost never met needs a different signal.
Small Live Test Before Full Capital
Start live trading with $50 to $100 USDC. This is enough to produce real fills, real slippage, and real gas costs while limiting downside on any early bugs. Run at this scale for one week. Compare the live results to the dry run logs. If fill quality, signal frequency, and P&L match expectations, scale up. If they diverge, investigate before adding capital.
Frequently Asked Questions
What VPS specs do I need for a Polymarket bot?
A basic arbitrage scanner runs on 2 cores and 4GB RAM. A market-making bot quoting across 20 or more markets needs at least 4 cores and 8GB RAM. If you run multiple bots or combine Polymarket trading with other platforms, plan for 6 cores and 16GB RAM minimum. NVMe SSD storage is non-negotiable for fast log writes and local data caching. A 3Gbps network connection prevents bandwidth saturation during volatile event windows.
Where are Polymarket’s servers located?
Polymarket’s CLOB API runs on AWS in the eu-west-2 region, which is London. The CLOB WebSocket endpoint is wss://ws-subscriptions-clob.polymarket.com/ws/ and the REST endpoint is https://clob.polymarket.com. Dublin and Amsterdam offer the lowest accessible latency for traders outside restricted regions. From a US-based server, round-trip latency to the CLOB runs around 130ms. That number matters for time-sensitive strategies like BTC 5-minute market trading.
What is the rate limit on the Polymarket CLOB API?
The CLOB API enforces 60 orders per minute per API key. Market data REST endpoints allow around 10 requests per second. For real-time data, use the WebSocket endpoint instead of polling REST. It delivers live orderbook deltas without counting against your REST rate limit. High-volume market makers can apply to Polymarket for elevated limits through the Builder Program.
What signature type should I use for my Polymarket bot wallet?
Use signature_type=0 for a standard EOA wallet like MetaMask. Use signature_type=1 for Polymarket email or Magic wallet accounts. Use signature_type=2 for Gnosis Safe or browser proxy wallets. The wrong signature type causes authentication failures that return HTTP 401 errors with no useful detail. Match your signature type exactly to how the wallet was created on Polymarket.
How do I keep my Polymarket bot running 24/7 on a VPS?
Set the bot up as a systemd service on Linux. Create a unit file at /etc/systemd/system/polybot.service, set Restart=always and RestartSec=5, and enable the service with systemctl enable polybot. The service will auto-restart on crashes and survive VPS reboots. A VPS with a 99.999% uptime SLA, like the TradoxVPS Polymarket VPS, keeps the underlying server online even during exchange event windows when your bot is most actively managing risk.
Do I need USDC or POL to run a Polymarket bot?
Yes, both. USDC is the settlement currency on Polymarket. Your trading capital sits in USDC on the Polygon network. POL (formerly MATIC) is the Polygon gas token. Each order submission costs a small amount of gas, typically fractions of a cent per transaction. Fund your wallet with enough POL to cover several weeks of gas. Around 5 to 10 POL is a sensible starting buffer for active bots.
What is a token allowance and why do I need to set it before trading?
Token allowances authorize the Polymarket smart contracts to move USDC and conditional tokens on your behalf. Without setting allowances, every order submission fails at the contract level even if your API authentication is correct. Run the official allowance setup script from Polymarket’s py-clob-client GitHub repository before placing your first order. You only need to do this once per wallet.
Can I run a Polymarket bot on a TradoxVPS Windows Server?
Yes. Windows Server 2022 supports Python 3.9 and above, pip, and all dependencies the py-clob-client SDK requires. Full administrator access on TradoxVPS plans lets you install any runtime, configure environment variables, and set up Task Scheduler as a process manager. For bots built in Rust, Windows compiles Cargo projects without issues. Linux deployments on TradoxVPS get the added benefit of systemd service management for auto-restart.
A Polymarket bot is only as good as its infrastructure. Dropped WebSocket connections mean stale quotes and adverse fills. A server reboot during a Fed announcement means unmanaged resting orders at the exact moment the market moves hardest. TradoxVPS’s Polymarket VPS in Dublin delivers under 0.5ms latency to Polymarket’s CLOB API, runs on AMD Ryzen 9 9950X processors with DDR5 RAM and NVMe storage, and backs every plan with a 99.999% uptime SLA and Path.net DDoS protection. Dublin is the closest unrestricted location to Polymarket’s London-based AWS infrastructure. That is not a marketing claim. It is a routing fact. See the full Dublin Polymarket VPS specifications and plans here.