Paper trading competition for Polymarket 15-minute up/down markets.
Trade BTC, ETH, SOL, XRP with realistic orderbook execution against live Polymarket data.
- Server tracks all trades with realistic orderbook execution
- Feed connects to Polymarket CLOB for live orderbook data + Binance for spot prices
- Your bot buys/sells shares throughout the 15-min window
- PnL is calculated on every trade (buy low, sell high)
- Leaderboard shows who's winning
Isolated execution: Each bot trades against the same real Polymarket orderbook, but trades don't affect other bots. This prevents bots from gaming each other.
# Setup
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Terminal 1: Start server
python server.py
# Terminal 2: Start price feed
python feed.py
# Terminal 3: Run a bot
cd examples
python random_bot.pyfrom sdk import UpDownArena
# Register and get API key
arena = UpDownArena.register("my_bot")
# Stream live ticks
async for tick in arena.stream():
print(f"{tick.asset}: UP={tick.price_up:.3f} | {tick.time_remaining:.0f}s left")
# Check spread before trading
if tick.spread_up and tick.spread_up < 0.02:
# Tight spread, good to trade
result = arena.buy(tick.asset, "UP", size=50)
print(f"Bought at {result.price:.3f} (slippage: {result.slippage:.4f})")
# Sell to realize PnL
result = arena.sell("BTC", "UP", size=50)
print(f"PnL: ${result.pnl:.2f}")
# Check positions
for pos in arena.positions():
print(f"{pos.asset} {pos.side}: {pos.shares:.2f} shares, unrealized: ${pos.unrealized_pnl:.2f}")Each tick includes Polymarket orderbook data and Binance spot/futures data:
| Field | Type | Range | Description |
|---|---|---|---|
price_up |
float |
0-1 | Midpoint price for UP token |
price_down |
float |
0-1 | Midpoint price for DOWN token |
best_bid_up |
float |
0-1 | Best bid for UP (sell price) |
best_ask_up |
float |
0-1 | Best ask for UP (buy price) |
best_bid_down |
float |
0-1 | Best bid for DOWN (sell price) |
best_ask_down |
float |
0-1 | Best ask for DOWN (buy price) |
spread_up |
float |
0-1 | ask - bid for UP token |
spread_down |
float |
0-1 | ask - bid for DOWN token |
time_remaining |
float |
0-900 | Seconds until market closes |
market_id |
str |
- | Condition ID for this 15-min window |
Full orderbook available via tick.orderbook:
| Field | Type | Description |
|---|---|---|
bids_up |
list[tuple[float, float]] |
[(price, size), ...] sorted high→low |
asks_up |
list[tuple[float, float]] |
[(price, size), ...] sorted low→high |
bids_down |
list[tuple[float, float]] |
[(price, size), ...] sorted high→low |
asks_down |
list[tuple[float, float]] |
[(price, size), ...] sorted low→high |
price: 0-1 (probability)size: shares available at that level
# Walk the UP ask levels
for price, size in tick.orderbook.get("asks_up", []):
print(f" {size:.2f} shares @ {price:.3f}")| Field | Type | Example | Description |
|---|---|---|---|
spot_price |
float |
95420.50 |
Current USDT spot price |
spot_change_1m |
float |
-0.12 |
% change last 1 min |
spot_change_5m |
float |
0.35 |
% change last 5 min |
spot_volume_1m |
float |
2500000.0 |
Volume last 1 min (USDT) |
funding_rate |
float |
0.01 |
Perpetual funding rate (%) |
open_interest |
float |
5e9 |
Open interest (USDT) |
# Buy $50 of UP shares
result = arena.buy("BTC", "UP", size=50)
# result.price = actual fill price (with slippage)
# result.shares = shares received
# result.slippage = difference from midpoint
# Sell shares
result = arena.sell("BTC", "UP", size=50)
# result.pnl = realized profit/loss
# result.proceeds = dollars receivedExecution model:
- All orders walk through the orderbook levels
- Fill price = volume-weighted average across levels consumed
- Orders exceeding available depth are rejected (insufficient liquidity)
| Endpoint | Method | Description |
|---|---|---|
/register |
POST | Register bot, get API key |
/markets |
GET | All active markets |
/market/<asset> |
GET | Single market (BTC, ETH, SOL, XRP) |
/trade |
POST | Execute trade {asset, action, side, size} |
/positions |
GET | Your positions with unrealized PnL |
/leaderboard |
GET | Rankings by total PnL |
/ws |
WS | Real-time tick stream |
/ |
GET | Web UI |
PnL is realized when you sell:
PnL = (sell_price - avg_buy_price) × shares_sold
- Buy at 0.50, sell at 0.55 → profit
- Buy at 0.50, sell at 0.45 → loss
- Leaderboard ranks by total realized PnL
At market resolution: Any remaining positions are auto-liquidated at the final price.
examples/random_bot.py- Random baselineexamples/momentum_bot.py- Follow price momentum
- Momentum: Buy UP when price_up is rising
- Mean reversion: Sell when price_up > 0.65, buy when < 0.35
- Spread trading: Only trade when spread is tight
- Orderbook imbalance: Use bid/ask depth signals
- Cross-asset: BTC leads, alts follow
- Time decay: Prices converge to 0 or 1 near expiry
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Polymarket │◄───│ │───►│ Binance │
│ (CLOB WSS) │ │ feed.py │ │ (spot data) │
└─────────────────┘ └────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│ Your Bot │───►│ server.py │
│ (SDK client) │◄───│ (orderbook exec)│
└─────────────────┘ └────────┬────────┘
│
▼
┌─────────────────┐
│ Leaderboard │
│ (Web UI) │
└─────────────────┘
MIT