Late Window Certainty
An algorithmic trading bot for Polymarket's 5-minute crypto Up/Down prediction markets. It waits until the outcome is near-certain, then buys the winning side.
The Strategy
Wait until it's obvious. Then bet.
Every 5 minutes, Polymarket opens binary markets for BTC, ETH, SOL, and DOGE: "Will the price be UP or DOWN vs the opening price at window close?"
The bot does not predict. It waits until 60-90 seconds before the window closes, checks what the price has actually done, and buys the side that's already winning. The token costs $0.85-$0.96 and pays $1.00 at resolution. A 4-10% return in 60 seconds.
The edge is timing, not prediction. By the time we enter, the price move has already happened. We're buying certainty at a discount — the market hasn't fully priced it in because there's still 60 seconds of theoretical uncertainty.
Architecture
Where everything lives.
scanner (Chainlink oracles)
└─ signal generator (8-10 factors)
└─ executor (CLOB API, FOK orders)
└─ dashboard (port 4482, JSON + web UI)
└─ bot-observe.mjs (cron → Supabase → djinn-site)Signal Scoring
10 factors. One score.
Each factor contributes to a 0-1 confidence score. The bot only trades when confidence exceeds the threshold (currently 0.6).
Stake sizing: 1% of portfolio at peak confidence, scaled linearly. Minimum $5.50 for live orders (CLOB enforces 5-share minimum at typical token prices).
Order Execution
FOK on the CLOB. No partial fills.
Orders are placed as Fill-or-Kill (FOK) via the Polymarket CLOB API. The bot traverses the sorted ask book to find the best available price, computes the exact share count to hit the target stake, and submits. If the order can't fill completely at the computed price, it's killed — no partial positions.
1. Scan CLOB orderbook for target side (UP/DOWN) 2. Walk sorted asks: find best price for our stake size 3. Compute shares = stake / price (must be >= 5 shares) 4. Sign order with deposit wallet (signature_type=3) 5. POST to CLOB API as FOK market buy 6. If matched → position open, await resolution 7. If no match → logged as failed signal (still track accuracy)
Most signals don't fill. Of ~500 signals over 7 days, only 75 matched on the CLOB. The unfilled signals still validate signal accuracy (94.9% correct) — the bottleneck is liquidity and timing, not prediction quality.
Wallet & Funds
Deposit wallet on Polygon.
Polymarket requires deposit wallets (signature_type=3) for all API traders. The deposit wallet is a smart contract deployed via the polymarket-client SDK. All approvals are set gaslessly via Builder API credentials.
The money loop
Observability
Three layers. Every 5 minutes.
The observability stack runs independently of the bot. If the bot crashes, the observer detects it and fires an alert.
bot-observe.mjs collect runs every 5 minutes via cron. Probes bot PID, dashboard HTTP, WireGuard VPN, CPU/memory/disk. Parses decisions.jsonl incrementally for new trades. Tails the log for error signatures. Writes everything to Supabase.
health → PID alive, dashboard up, portfolio, mode vpn → WireGuard handshake age, transfer bytes resource → CPU %, memory RSS, disk free trade → each signal/order from decisions.jsonl error → grouped log error signatures config → bot.py source parse (on change only)
Slack alerts with per-condition cooldowns so you're never spammed.
The /trading dashboard has two views: Live (real-time market state, window timer, signals) and Observability (system health, performance stats, trade journal, error signatures). Data from Supabase via Next.js API route. Auto-refreshes every 5 seconds.
Auto-Improver
DJINN improves the bot autonomously.
integrator-watch.mjs runs hourly. When new commits appear in the source repo, it triggers a full research-and-build cycle on a fork.
Two repos: integrator-research (Dima's source, read-only) and integrator-work (the fork, DJINN writes here). DJINN never writes to the source repo.
Operations
Running, stopping, monitoring.
cd /root/integrator-research/experiments/23-polymarket-5m export $(grep -v '^#' .env | xargs) PYTHONUNBUFFERED=1 nohup .venv/bin/python -u bot.py \ --live --port 4482 --portfolio 370 > /tmp/polymarket-bot.log 2>&1 & echo $! > /tmp/polymarket-bot.pid
tail -f /tmp/polymarket-bot.log # live log curl -s http://localhost:4482/api/state | python3 -m json.tool # dashboard JSON kill $(cat /tmp/polymarket-bot.pid) # stop # DJINN observability CLI node packages/djinn/bot-observe.mjs status # health snapshot node packages/djinn/bot-observe.mjs performance --window 7d # filled + signals node packages/djinn/bot-observe.mjs trades --mode live # live trades only node packages/djinn/bot-observe.mjs errors # grouped error signatures
*/5 * * * * bot-observe.mjs collect # probe + telemetry 30 0 * * * bot-observe.mjs daily-summarize # Claude daily digest 0 * * * * integrator-watch.mjs # auto-improve loop */10 * * * * resolve_outcomes.py --quiet # paper trade resolution
VPN
WireGuard to Japan.
The droplet is in Amsterdam — geoblocked by Polymarket. A ProtonVPN WireGuard tunnel routes traffic through Tokyo. The PostUp rule preserves SSH access so the tunnel never kills your connection.
wg-quick up wg-polymarket # start wg-quick down wg-polymarket # stop wg show wg-polymarket # status (check "transfer: X received") curl -s https://polymarket.com/api/geoblock # verify geo (should show JP)
If VPN kills SSH: reboot via doctl compute droplet-action reboot 551550136 or the DigitalOcean web console. The VPN is not systemd-enabled so it won't persist across reboot. If the tunnel dies (0 bytes received), generate a new config at account.protonvpn.com (WireGuard, any Japan paid server, keep PostUp/PostDown lines).
Lessons Learned
What we learned the hard way.
Polymarket requires signature_type=3 for all new API users. EOA mode (type=0) is rejected. Don't waste time.
AsyncSecureClient.create() auto-deploys the deposit wallet and sets all exchange approvals gaslessly. py-clob-client can derive keys but can't deploy.
The web UI creates a SESSION SIGNER. Relayer API keys from Settings bind to it, not your wallet. Keys from the web UI don't work for bot trading.
The deposit wallet needs pUSD. Path: USDC -> USDC.e (Uniswap) -> pUSD (Polymarket bridge). CollateralOnramp wrap() does NOT accept native USDC.
94.9% signal accuracy but only 75/497 orders matched. The bottleneck is CLOB liquidity, not prediction quality. Track both separately.
Without the WireGuard PostUp rule routing SSH through eth0, VPN kills your connection to the droplet. Always keep it.
Phantom can silently create a new default wallet on update. The old wallet is still there — switch accounts. Funds in Polymarket are tied to the wallet address, not the session.