PolyTrader28
Exploits pricing discrepancies between Binance spot prices and Polymarket 15-minute prediction market contracts using dual arbitrage strategies.
01 The Problem
Cryptocurrency markets are fragmented. The same asset — Bitcoin or Ethereum — trades at slightly different prices across different exchanges, and prediction markets add another dimension of price discovery entirely. Polymarket's 15-minute binary contracts (e.g. "Will BTC exceed $85,000 at 14:30 UTC?") create a unique arbitrage opportunity: their settlement price is pinned to the Binance spot price at expiry, but in the minutes before settlement the contract price drifts due to trader sentiment, liquidity imbalances, and information asymmetry.
The problem is speed. These windows last seconds, not minutes. A manual trader simply cannot monitor both venues, calculate fair value, and execute an order before the opportunity vanishes. PolyTrader28 solves this by coupling real-time WebSocket streams from both platforms with algorithmic strategy execution — all in sub-second loops.
02 Architecture
PolyTrader28 is organized into four logical layers, each with clear responsibilities. Data ingestion runs in separate asyncio tasks, the strategy layer processes signals synchronously, and the execution layer manages risk and order lifecycles.
PolyTrader28/
│
├── Data Layer
│ ├── binance_stream.py — WebSocket client for live BTC/ETH prices
│ └── polymarket_api.py — Polymarket CLOB V2 + Gamma API client
│
├── Strategy Layer
│ ├── price_lag.py — Strategy A: Time arbitrage (Binance vs Polymarket)
│ └── complete_set.py — Strategy B: Risk-free YES+NO complete-set arbitrage
│
├── Execution Layer
│ ├── order_manager.py — Order placement, fill monitoring, position management
│ └── risk_manager.py — Position sizing, stop-loss, drawdown limits, compounding
│
├── Python Files
│ ├── polymarket_bot.py — Main orchestrator — main loop, lifecycle, signal handling
│ ├── config.py — Configuration loaded from .env (singleton)
│ ├── models.py — SQLite database layer (trades, equity, ticks, opportunities)
│ └── dashboard.py — Flask web dashboard + REST API
│
└── Utilities
├── logger.py — Rotating file + console logger
└── telegram_alerts.py — Telegram bot notifications
Every layer is independently testable. The data layer can be swapped for a backtest feed, strategies operate on a generic PriceSignal interface, and the execution layer enforces risk limits before any order reaches the exchange. This separation made the backtesting engine trivial to build — it just replaces the WebSocket streams with historical tick data.
03 Tech Stack
Every component was chosen for low latency, reliability, and easy debugging. Python 3.14's improved asyncio performance made it viable for real-time trading without dropping into C++ or Rust.
| Technology | Role | Why |
|---|---|---|
| Python 3.14 | Core runtime | Asyncio-native, fast dict/list, excellent library ecosystem |
| Binance WebSocket | Live price feed | 100ms granularity BTC/ETH streams, low-latency push |
| Polymarket CLOB V2 SDK | Order book & execution | Official SDK for order placement, fills, positions |
| Flask | Dashboard + REST API | Lightweight, well-understood, easy to extend |
| SQLite | Persistence | Zero-config, sufficient for single-instance trading |
| Telegram Bot API | Alerts & notifications | Mobile push for fills, errors, opportunities |
04 Key Challenges
⚙ 1. Dual WebSocket Management
Maintaining persistent connections to both Binance and Polymarket simultaneously is non-trivial. One stale connection can miss an entire arbitrage window. The solution uses a custom reconnection loop with exponential backoff, heartbeat pings every 10 seconds, and a health-check thread that force-restarts a stream if no data arrives for 30 seconds. Connection state is logged to SQLite for post-mortem debugging.
⏰ 2. Strategy Timing
Polymarket's 15-minute contracts require millisecond precision. A price discrepancy that exists at t = 0 can vanish by t = 0.5 seconds. The entire pipeline — price detection → signal generation → order placement — must complete in under 200ms. This meant avoiding synchronous I/O in the critical path, pre-computing contract metadata at startup, and batching order submissions.
The hot loop is written entirely in asyncio with zero blocking calls. Configuration is loaded once into a frozen dataclass, contract lookups use an in-memory dict indexed by token ID, and order payloads are pre-serialized. A full iteration (both exchanges checked, both strategies evaluated) averages 85ms on a standard VPS.
🛡 3. Risk Management
Crypto trading has black-swan events. The risk manager must handle exchange outages, rapid price movements, Polymarket settlement failures, and gas price spikes. Position sizing uses the Kelly criterion to optimize growth while preventing ruin. Circuit breakers pause trading if drawdown exceeds 15% in a single session or if the exchange API returns unexpected errors. All positions are limited to a configurable fraction of the portfolio (default: 2% per position).
📊 4. Backtesting Accuracy
Historical backtesting must account for Polymarket's unique settlement mechanism, not just price movements. Slippage, fill probability, and gas costs must all be modeled. The backtesting engine replays tick data through the same strategy code used in production, then compares executed vs. ideal fills using a configurable slippage model. Results are output as PNG equity curves and CSV trade logs.
⚡ Code Highlight
The price-lag strategy uses a logistic function to convert real-time Binance price movements into implied probabilities, then compares them against Polymarket's market pricing to detect arbitrage.
LOGISTIC_K = 500.0 # Steepness — higher = more sensitive MIN_EDGE = 0.015 # Minimum probability edge to trade class PriceLagStrategy: def evaluate(self, symbol: str, current_price: float, period_open: float, polymarket_yes: float) -> dict | None: # Logistic: map price change → 0-1 probability price_change = (current_price - period_open) / period_open implied_up_prob = 1 / (1 + math.exp(-LOGISTIC_K * price_change)) # Compare with Polymarket's YES price (market-implied prob) edge = abs(implied_up_prob - polymarket_yes) if edge >= MIN_EDGE: side = "YES" if implied_up_prob > polymarket_yes else "NO" return {"symbol": symbol, "side": side, "edge": edge, "size": self._kelly_size(edge)}
05 Results
PolyTrader28 is production-ready. The dual-strategy arbitrage engine operates in both dry-run (paper trading) and live mode. The Flask dashboard provides real-time P&L, open positions, and opportunity logs. The backtesting engine replays historical data through the same strategy code, generating equity curves and CSV trade logs for analysis.
- Dual strategy arbitrage engine — Time-lag (Strategy A) and complete-set (Strategy B) both operational
- WebSocket pipeline — Automatic reconnection with exponential backoff and health monitoring
- Flask dashboard — Real-time P&L, trading activity, and system health at a glance
- Backtesting engine — Historical replay with slippage modeling and PNG equity curve output
- Risk management — Kelly position sizing, drawdown limits, and automated circuit breakers
- Telegram alerts — Instant push notifications for fills, errors, and detected opportunities
06 What I Learned
Building PolyTrader28 was a deep dive into the realities of algorithmic trading — far removed from the simplified models in textbooks. Three lessons stand out:
Latency is architecture, not optimization. You can't bolt speed onto a synchronous design. The entire data path — from WebSocket message to strategy decision — must be non-blocking from day one. Thread-pools and queues add unpredictable jitter.
Prediction market arbitrage is fundamentally different from CEX arbitrage. Settlement mechanics, binary outcomes, and the fixed 15-minute expiry window create an entirely different risk profile. You're not betting on direction — you're betting on the efficiency of the market's pricing mechanism.
Backtesting is a simulation of your model, not reality. Slippage, fill probability, gas costs, and exchange latency all compound in ways that simple historical replay cannot capture. PolyTrader28's configurable slippage model was essential — but even then, the only test that matters is live trading with small capital.