Automated arbitrage detection and execution on Polymarket prediction markets using the CLOB API.
The bot continuously:
- Fetches active markets from the Polymarket Gamma API
- Scans orderbooks for each market's outcomes via the CLOB API
- Detects arbitrage — when buying all outcomes costs less than $1.00 (the guaranteed payout) after fees
- Executes trades — buys all legs of a profitable arb (or logs in dry-run mode)
A binary Yes/No market where:
- Best ask for YES = $0.45
- Best ask for NO = $0.50
- Fees (per-market) = $0.000
- Total cost = $0.950
- Profit = $0.050 per share (buy both -> guaranteed $1.00 payout)
pip install -r requirements.txtOption A — Generate a new wallet:
python generate_wallet.pySave the private key securely, then delete the script.
Option B — Use an existing wallet (e.g. Phantom):
Export your private key from Phantom (Settings -> Security -> Export Private Key). Convert it to hex format with 0x prefix if needed.
- Import your wallet into a browser extension (MetaMask, Phantom, Rabby, etc.)
- Go to polymarket.com and connect the wallet
- Complete onboarding and deposit USDC
- Find your funder/proxy address: click Deposit -> copy the deposit address (starts with
0x)
Copy the example env file and fill in your credentials:
cp .env.example .envEdit .env:
PRIVATE_KEY— Your wallet's private key (hex, 0x-prefixed)FUNDER_ADDRESS— Your Polymarket proxy/deposit address (from the Deposit page)- Adjust strategy parameters as needed
Security: Never commit .env. The .gitignore already excludes it.
python test_connection.pyThis checks that your key, funder address, and API auth all work. It also fetches a sample orderbook.
python check_balance.py # CLOB trading balance (collateral)
python check_onchain.py # On-chain USDC/POL balance at FUNDER_ADDRESSpython bot.pyBy default, DRY_RUN=true -- the bot will scan and log opportunities without placing real orders. Once you're comfortable, set DRY_RUN=false in .env to enable live trading.
| Variable | Default | Description |
|---|---|---|
PRIVATE_KEY |
— | Your wallet private key |
FUNDER_ADDRESS |
— | Polymarket funder/proxy address |
CLOB_HOST |
https://clob.polymarket.com |
CLOB API endpoint |
CHAIN_ID |
137 |
Polygon mainnet |
MIN_PROFIT_MARGIN |
0.005 |
Min profit per share to trigger arb ($) |
MIN_ROI |
0.004 |
Min ROI per arb (profit / total cost) |
MIN_FILL_CONFIDENCE |
0.35 |
Min fill confidence score for opportunity |
MAX_POSITION_SIZE |
10.0 |
Max USDC per single trade |
MAX_TOTAL_EXPOSURE |
100.0 |
Max total USDC across all positions |
MAX_TRADES_PER_CYCLE |
3 |
Max successful executions per scan cycle |
SCAN_INTERVAL |
10 |
Seconds between scans |
MIN_BOOK_DEPTH |
5 |
Min shares on ask side to consider |
DRY_RUN |
true |
Log-only mode (no real trades) |
LOG_LEVEL |
INFO |
DEBUG/INFO/WARNING/ERROR |
MIN_COST_THRESHOLD |
0.90 |
Skip arbs with total cost below this |
MAX_PROFIT_PER_SHARE |
0.05 |
Skip arbs with profit/share above this |
MIN_ARB_VALUE |
0.50 |
Skip arbs with estimated profit below this |
MIN_MARKET_LIQUIDITY |
1000 |
Gamma liquidity filter |
WS_ENABLED |
true |
Use WebSocket mode |
WS_HOST |
wss://ws-subscriptions-clob.polymarket.com/ws/market |
WebSocket host |
HEARTBEAT_ENABLED |
true |
Dead-man switch on/off |
HEARTBEAT_INTERVAL |
5 |
Heartbeat interval (seconds) |
USE_FOK_ORDERS |
true |
Fill-or-kill orders |
USE_BATCH_ORDERS |
true |
Submit all legs at once |
GTD_EXPIRY_SECONDS |
30 |
GTD expiry (0 = GTC) |
REQUIRE_FULL_MATCH |
true |
Require every leg to be immediately matched |
FAILURE_COOLDOWN_SECONDS |
20 |
Cooldown after failed market execution |
BALANCE_CHECK_ENABLED |
true |
Block trades if balance low |
├── bot.py — Main loop (entry point)
├── config.py — Loads .env settings
├── client.py — Polymarket CLOB client wrapper
├── markets.py — Market discovery via Gamma API
├── arbitrage.py — Arbitrage detection engine
├── executor.py — Trade execution with safety checks
├── risk.py — Position/exposure limits
├── logger.py — Logging setup
├── generate_wallet.py — One-time wallet generator (delete after use)
├── test_connection.py — Verify API credentials & connectivity
├── check_balance.py — Check CLOB trading balance
├── check_onchain.py — Check on-chain USDC/POL balances
├── derive_funder.py — Derive funder address from private key
├── requirements.txt
├── .env.example — Config template
└── .gitignore
- You can lose money. Arbitrage on prediction markets is competitive — opportunities are rare and fleeting. Slippage, partial fills, and timing can turn a theoretical profit into a loss.
- Start with
DRY_RUN=trueand tiny position sizes. - Never expose your private key. Use env vars only, never hardcode.
- This is not financial advice. Use at your own risk.
Polymarket uses two addresses:
- Signer EOA: derived from your
PRIVATE_KEY. This signs orders. - Funder/Proxy: shown on the Polymarket Deposit page. This holds funds.
These must belong to the same Polymarket account. If they do not match, you will see
invalid signature errors when placing orders.
Quick check:
python derive_funder.pyCompare the printed Signer EOA with the wallet you connect on Polymarket and make sure the Funder (env) address matches the Deposit/Proxy address for that same wallet.