Execute trades on Polymarket using py_clob_client - full API access for market data, orders, positions
Full access to Polymarket's CLOB (Central Limit Order Book) via the official py_clob_client library.
60+ methods documented. This is the complete reference.
PRIVATE_KEY=0x... # Ethereum private key for signing
POLY_FUNDER_ADDRESS=0x... # Your wallet address on Polygon
POLY_API_KEY=... # From Polymarket API
POLY_API_SECRET=... # Base64 encoded secret
POLY_API_PASSPHRASE=... # API passphrase
pip install py-clob-client requests
| Level | Requirements | Capabilities |
|---|---|---|
| L0 | None | Read-only: orderbooks, prices, markets |
| L1 | Private key | Create & sign orders (not post) |
| L2 | Private key + API creds | Full trading: post orders, cancel, query |
| Type | Use Case |
|---|---|
0 | Standard EOA (MetaMask, hardware wallets) |
1 | Magic/email wallets (delegated signing) |
2 | Proxy wallets (Gnosis Safe, browser proxy) |
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import (
OrderArgs, MarketOrderArgs, ApiCreds, OrderType,
BookParams, TradeParams, OpenOrderParams, BalanceAllowanceParams,
AssetType, OrderScoringParams, OrdersScoringParams, DropNotificationParams
)
from py_clob_client.order_builder.constants import BUY, SELL
from py_clob_client.constants import POLYGON # 137
# Level 2 Auth (full trading access)
client = ClobClient(
host="https://clob.polymarket.com",
key=os.getenv("PRIVATE_KEY"), # Private key for signing
chain_id=POLYGON, # 137 for mainnet, 80002 for Amoy testnet
funder=os.getenv("POLY_FUNDER_ADDRESS"), # Wallet address (for proxy wallets)
signature_type=2 # 0=EOA, 1=MagicLink, 2=Proxy
)
# Set API credentials for authenticated endpoints
client.set_api_creds(ApiCreds(
api_key=os.getenv("POLY_API_KEY"),
api_secret=os.getenv("POLY_API_SECRET"),
api_passphrase=os.getenv("POLY_API_PASSPHRASE")
))
client.get_ok() # Check if server is up
client.get_server_time() # Get server timestamp
client.get_address() # Your signer's public address
client.get_collateral_address() # USDC contract address
client.get_conditional_address() # CTF contract address
client.get_exchange_address() # Exchange contract (neg_risk=False default)
# Get prices and spreads
client.get_midpoint(token_id) # Mid market price
client.get_price(token_id, side="BUY") # Best price for side
client.get_spread(token_id) # Current spread
client.get_last_trade_price(token_id) # Last executed trade price
# Get full orderbook
orderbook = client.get_order_book(token_id)
# Returns: OrderBookSummary with bids, asks, tick_size, neg_risk, timestamp, hash
params = [
BookParams(token_id="TOKEN1", side="BUY"),
BookParams(token_id="TOKEN2", side="SELL")
]
client.get_midpoints(params) # Multiple midpoints
client.get_prices(params) # Multiple prices
client.get_spreads(params) # Multiple spreads
client.get_order_books(params) # Multiple orderbooks
client.get_last_trades_prices(params) # Multiple last prices
client.get_tick_size(token_id) # Returns: "0.1", "0.01", "0.001", or "0.0001"
client.get_neg_risk(token_id) # Returns: True/False (negative risk market)
client.get_fee_rate_bps(token_id) # Returns: fee rate in basis points (0 or 1000)
from py_clob_client.clob_types import OrderType
OrderType.GTC # Good Till Cancelled - stays open until filled/cancelled
OrderType.FOK # Fill Or Kill - fill entirely immediately or cancel
OrderType.GTD # Good Till Date - expires at timestamp (min 60 seconds)
OrderType.FAK # Fill And Kill - fill what's possible, cancel rest
| Type | Use Case | Example |
|---|---|---|
| GTC | Entries - wait for fill | Place buy at 0.45, wait for dip |
| FOK | Exits - need immediate fill | Market sell entire position NOW |
| GTD | Time-limited orders | Offer expires in 5 minutes |
| FAK | Partial fills OK | Get as much as possible now |
OrderArgs(
token_id: str, # Token ID (outcome to trade)
price: float, # Price 0.01-0.99
size: float, # Number of shares
side: str, # "BUY" or "SELL" (or use BUY/SELL constants)
fee_rate_bps: int = 0, # Optional: fee rate in bps (0 or check market)
nonce: int = 0, # Optional: unique nonce for cancellation
expiration: int = 0, # Optional: expiry timestamp (0 = GTC, use timestamp for GTD)
taker: str = ZERO_ADDRESS # Optional: specific taker (ZERO_ADDRESS = anyone)
)
MarketOrderArgs(
token_id: str, # Token ID
amount: float, # Total USDC amount to spend (BUY) or shares (SELL)
side: str, # "BUY" or "SELL"
price: float = 0, # Optional: worst acceptable price (slippage protection)
fee_rate_bps: int = 0, # Optional: fee rate
nonce: int = 0, # Optional: nonce
taker: str = ZERO_ADDRESS, # Optional: taker address
order_type: OrderType = FOK # Optional: FOK (default) or FAK
)
from py_clob_client.order_builder.constants import BUY, SELL
# 1. LIMIT BUY (GTC) - sits on book until filled
order = client.create_and_post_order(
OrderArgs(token_id=TOKEN_ID, price=0.45, size=100.0, side=BUY)
)
# 2. LIMIT SELL (GTC)
order = client.create_and_post_order(
OrderArgs(token_id=TOKEN_ID, price=0.55, size=50.0, side=SELL)
)
# 3. MARKET BUY - spend $100 USDC at current prices (FOK)
signed = client.create_market_order(
MarketOrderArgs(token_id=TOKEN_ID, amount=100.0, side=BUY)
)
result = client.post_order(signed, orderType=OrderType.FOK)
# 4. MARKET SELL - sell all shares immediately (FOK)
signed = client.create_market_order(
MarketOrderArgs(token_id=TOKEN_ID, amount=my_shares, side=SELL)
)
result = client.post_order(signed, orderType=OrderType.FOK)
# 5. POST-ONLY MAKER ORDER (avoid taker fees, earn rebates)
signed = client.create_order(
OrderArgs(token_id=TOKEN_ID, price=0.44, size=100.0, side=BUY)
)
result = client.post_order(signed, orderType=OrderType.GTC, post_only=True)
# If order would cross spread, it gets REJECTED instead of taking
# 6. GOOD TIL DATE (GTD) - expires after timestamp
import time
expiry = int(time.time()) + 300 # 5 minutes from now
signed = client.create_order(
OrderArgs(token_id=TOKEN_ID, price=0.50, size=100.0, side=BUY, expiration=expiry)
)
result = client.post_order(signed, orderType=OrderType.GTD)
# 7. FILL AND KILL (FAK) - fill what you can, cancel rest
signed = client.create_market_order(
MarketOrderArgs(token_id=TOKEN_ID, amount=1000.0, side=BUY)
)
result = client.post_order(signed, orderType=OrderType.FAK)
# SIMPLE: Create and post in one call (recommended)
result = client.create_and_post_order(
OrderArgs(
token_id="123456789012345678901234567890",
price=0.45,
size=10.0,
side="BUY"
)
)
# Returns: {"orderID": "...", "status": "...", ...}
# ADVANCED: Separate create and post
order = client.create_order(OrderArgs(...)) # Returns SignedOrder
result = client.post_order(order, orderType=OrderType.GTC, post_only=False)
# Market order (calculates price automatically)
result = client.create_market_order(
MarketOrderArgs(
token_id="...",
amount=100.0, # Spend $100 USDC
side="BUY"
)
)
# Calculate expected fill price before market order
price = client.calculate_market_price(
token_id="...",
side="BUY",
amount=100.0,
order_type=OrderType.FOK
)
client.cancel(order_id="ORDER_ID") # Cancel specific order
client.cancel_orders(["ID1", "ID2", "ID3"]) # Cancel multiple
client.cancel_all() # Cancel ALL open orders
client.cancel_market_orders( # Cancel by market/asset
market="CONDITION_ID",
asset_id="TOKEN_ID"
)
# Get all open orders
orders = client.get_orders(
params=OpenOrderParams(
id="ORDER_ID", # Optional: specific order
market="COND_ID", # Optional: filter by market
asset_id="TOKEN" # Optional: filter by token
),
next_cursor="MA==" # For pagination
)
# Get specific order
order = client.get_order(order_id="ORDER_ID")
trades = client.get_trades(
params=TradeParams(
id="TRADE_ID", # Optional: specific trade
maker_address="0x...", # Optional: filter by maker
market="CONDITION_ID", # Optional: filter by market
asset_id="TOKEN_ID", # Optional: filter by token
before="2024-01-01", # Optional: before date
after="2023-01-01" # Optional: after date
),
next_cursor="MA=="
)
# Check balance and allowances
balance = client.get_balance_allowance(
params=BalanceAllowanceParams(
asset_type=AssetType.COLLATERAL, # USDC balance
# or AssetType.CONDITIONAL # Token balance
token_id="TOKEN_ID" # For conditional tokens
)
)
# Update/refresh allowance cache
client.update_balance_allowance(params=...)
# All active markets
markets = client.get_markets(next_cursor="MA==")
simplified = client.get_simplified_markets()
# Specific market
market = client.get_market(condition_id="CONDITION_ID")
# Market trade events
events = client.get_market_trades_events(condition_id="CONDITION_ID")
# Sampling/featured markets
client.get_sampling_markets()
client.get_sampling_simplified_markets()
import requests
def search_markets(query: str, limit: int = 10):
"""Search Polymarket markets by keyword"""
url = "https://gamma-api.polymarket.com/markets"
params = {
"_q": query,
"active": "true",
"closed": "false",
"_limit": limit
}
r = requests.get(url, params=params)
return r.json()
# Get market details
markets = search_markets("bitcoin")
for m in markets:
print(f"Question: {m['question']}")
print(f"Condition ID: {m['condition_id']}")
print(f"Volume: ${m.get('volume', 0):,.2f}")
for token in m.get('tokens', []):
print(f" {token['outcome']}: {token['token_id']}")
print(f" Price: {float(token['price']):.2f}")
import requests
CTF_CONTRACT = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
RPC_URL = "https://polygon-rpc.com/"
def get_token_balance(wallet: str, token_id: str) -> float:
"""Get balance of a specific outcome token in shares"""
token_int = int(token_id)
# ERC-1155 balanceOf(address,uint256)
data = f"0x00fdd58e000000000000000000000000{wallet[2:].lower()}{token_int:064x}"
r = requests.post(RPC_URL, json={
"jsonrpc": "2.0",
"method": "eth_call",
"params": [{"to": CTF_CONTRACT, "data": data}, "latest"],
"id": 1
})
result = r.json().get("result", "0x0")
balance = int(result, 16) / 1e6 # Convert from raw to shares
return balance
# Usage
balance = get_token_balance(
wallet="0x7c2211103e7Fbb257Ac6fa59f972cfd8bc9D4795",
token_id="12345678901234567890"
)
print(f"Position: {balance} shares")
USDC_CONTRACT = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
def get_usdc_balance(wallet: str) -> float:
"""Get USDC balance on Polygon"""
# ERC-20 balanceOf(address)
data = f"0x70a08231000000000000000000000000{wallet[2:].lower()}"
r = requests.post(RPC_URL, json={
"jsonrpc": "2.0",
"method": "eth_call",
"params": [{"to": USDC_CONTRACT, "data": data}, "latest"],
"id": 1
})
result = r.json().get("result", "0x0")
return int(result, 16) / 1e6 # USDC has 6 decimals
# Create new API key
creds = client.create_api_key(nonce=0)
# Derive existing API key (if you lost creds but have private key)
creds = client.derive_api_key(nonce=0)
# Create or derive (tries both)
creds = client.create_or_derive_api_creds(nonce=0)
# Get all your API keys
keys = client.get_api_keys()
# Delete current API key
client.delete_api_key()
# Readonly API keys (for monitoring only)
readonly = client.create_readonly_api_key()
client.get_readonly_api_keys()
client.delete_readonly_api_key(key="...")
client.validate_readonly_api_key(address="0x...", key="...")
# Start heartbeat - if not sent within 10s, all orders cancelled
heartbeat_id = client.post_heartbeat(heartbeat_id=None)
# Continue sending heartbeats
while trading:
client.post_heartbeat(heartbeat_id=heartbeat_id)
time.sleep(5)
# Check if order is scoring (earning rewards)
is_scoring = client.is_order_scoring(
params=OrderScoringParams(order_id="...")
)
# Check multiple orders
scores = client.are_orders_scoring(
params=OrdersScoringParams(order_ids=["ID1", "ID2"])
)
notifications = client.get_notifications()
client.drop_notifications(params=DropNotificationParams(...))
IMPORTANT: Most Polymarket markets have ZERO fees (0% maker, 0% taker).
Only 15-minute BTC/ETH/SOL/XRP price prediction markets have fees:
fee = shares × 0.25 × (price × (1 - price))²
| Entry Price | Fee % (per side) |
|---|---|
| 0.50 | ~1.56% |
| 0.60 or 0.40 | ~1.44% |
| 0.70 or 0.30 | ~1.10% |
| 0.80 or 0.20 | ~0.64% |
| 0.90 or 0.10 | ~0.20% |
TAKER = crosses spread = PAYS fee MAKER = adds liquidity = NO fee + earns rebates
To be a maker: Post orders that don't immediately fill (inside the spread).
| Order Side | Price Decimals | Size Decimals |
|---|---|---|
| BUY | 2 | 4 |
| SELL | 2 | 2 |
Min order size: $1 per side
#!/usr/bin/env python3
"""
Production-ready Polymarket trading script
"""
import os
import time
import requests
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs, ApiCreds, OrderType
# Initialize
client = ClobClient(
"https://clob.polymarket.com",
key=os.getenv("PRIVATE_KEY"),
chain_id=137,
funder=os.getenv("POLY_FUNDER_ADDRESS"),
signature_type=2
)
client.set_api_creds(ApiCreds(
api_key=os.getenv("POLY_API_KEY"),
api_secret=os.getenv("POLY_API_SECRET"),
api_passphrase=os.getenv("POLY_API_PASSPHRASE")
))
TOKEN_ID = "YOUR_TOKEN_ID"
WALLET = os.getenv("POLY_FUNDER_ADDRESS")
CTF = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
def get_balance(token_id):
"""Get position size"""
token_int = int(token_id)
data = f"0x00fdd58e000000000000000000000000{WALLET[2:].lower()}{token_int:064x}"
r = requests.post("https://polygon-rpc.com/", json={
"jsonrpc": "2.0", "method": "eth_call",
"params": [{"to": CTF, "data": data}, "latest"], "id": 1
})
return int(r.json().get("result", "0x0"), 16) / 1e6
def get_orderbook(token_id):
"""Get current bid/ask"""
book = client.get_order_book(token_id)
return {
"best_bid": float(book.bids[0].price) if book.bids else 0,
"best_ask": float(book.asks[0].price) if book.asks else 1
}
# Check position
position = get_balance(TOKEN_ID)
print(f"Current position: {position} shares")
# Get market
book = get_orderbook(TOKEN_ID)
print(f"Bid: {book['best_bid']:.2f}, Ask: {book['best_ask']:.2f}")
# Place a buy order (maker - inside spread)
buy_price = book['best_bid'] + 0.01 # 1 cent above bid
if buy_price < book['best_ask']: # Ensure we're maker
result = client.create_and_post_order(OrderArgs(
token_id=TOKEN_ID,
price=buy_price,
size=10.0,
side="BUY"
))
print(f"Buy order placed: {result}")
# Place a sell order (market sell via FOK)
if position > 0:
result = client.create_and_post_order(OrderArgs(
token_id=TOKEN_ID,
price=0.01, # Lowest price = immediate fill
size=position,
side="SELL"
))
print(f"Sold position: {result}")
from py_clob_client.exceptions import PolyApiException