Automated trading bot for Polymarket's 5-minute Bitcoin Up/Down binary markets.
Live trading with safety filters. Previous version lost ~$56 from $130 deposited due to ghost fills, adverse selection, and bad trade filtering. v2 adds strict entry filters based on analysis of 162 real trades.
- Balance: $74.28 USDC
- Proxy wallet:
0x45bfb3aB984aFDA6c801b4a3cD5126c16926E42E - Real performance (v1): 159 markets, 65.4% WR, -$96.64 PnL
Polymarket runs a new BTC Up/Down market every 5 minutes (288/day). Each resolves to "Up" if BTC ends the window >= start price, "Down" otherwise.
The bot uses a GBM volatility model to estimate the true probability of each outcome. When model confidence diverges enough from market pricing, it places maker limit orders.
P_up = Phi(ln(S_t / S_0) / (sigma * sqrt(tau)) * 0.95)
S_0= BTC price at window startS_t= current BTC price (Binance websocket, real-time)tau= seconds remaining in windowsigma= EWMA per-second volatility (10-min lookback)0.95= mean reversion dampening factor
| Filter | Value | Reason |
|---|---|---|
P_ENTRY |
>= 0.84 | Minimum model confidence |
MIN_BUY_PRICE |
>= $0.70 | $0.65-0.70 bucket was 41% WR, -$43 total |
MIN_EDGE |
>= 0.00 | Negative edge trades lost $20.75 |
MAX_TAU |
<= 250s | Early entries (280s+) had high reversal rate |
MIN_TAU |
>= 30s | Too close to expiry = no time for fill |
SKIP_HOURS |
0, 3 | Midnight=50% WR, 3AM=63% WR |
VOL_KILL |
0.05-0.30% | Skip extreme volatility |
Expected impact: ~13 trades/day (vs ~160 before), 84% WR on filtered subset.
- Fill verification: After placing GTC limit order, waits 3s then checks
get_order_fill(). Cancels unfilled orders. - Post-expiry phantom check: Re-verifies order status after window expires. Skips trades with
filled=0orCANCELED. - Redemption verification: Checks USDC balance before/after
redeem_positions(). If $0 received, adjusts internal bankroll. - Kelly sizing: Scaled Kelly fraction (0.05-0.50), sized off internal bankroll.
- Ghost fills: GTC limit orders can show MATCHED but deliver 0 shares. Always verify
size_matched. - Adverse selection: Winning limit orders don't fill (price moves away), losing orders always fill. Paper WR (77%) ≠ real WR (65.4%).
- Silent redemption failures:
redeem_positions()can return True but deliver $0. - BTC price ≠ market resolution: Polymarket's oracle can resolve differently than our Binance feed.
- PnL must be verified on-chain, not assumed from internal logic.
live_trader.py Main async loop, filters, Kelly sizing, order management
config.py Constants, env vars
price_feed.py Binance BTC/USDT websocket + EWMA volatility
model.py GBM probability model, fee calculations
market.py Polymarket CLOB client (orders, books, redemptions)
backtest_realistic.py Backtester with adverse selection modeling
replay_filtered.py Replay historical trades through filter configs
data/live_trades.csv Active trade log (clean, v2)
data/live_trades_OLD_GHOST_FILLS.csv v1 trades (includes ghost fills, unreliable)
data/btc_prices_2s.csv BTC price history (2s intervals, 143k rows)
logs/book_snapshots.csv Order book snapshots (39k rows)
uv venv && source .venv/bin/activate
uv pip install -r requirements.txtRequires ../.env with:
PK=<polygon_private_key>
# Start live trading
cd btc-bot
.venv/bin/python live_trader.py
# Monitor
tail -f data/live_trader.log
cat data/live_trades.csv- Starting bankroll: $74.28 (reset Feb 19, 2026)
- Kelly fraction: 0.05-0.50 (scaled by bankroll growth)
- Min bet: 5 shares / $1
- Target: $9/day profit ($270/month to cover inference costs)