5m
Polymarket

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.

Signal Accuracy
94.9%
462 correct / 25 wrong
Filled Orders
75
68W / 6L / 91.9%
Assets
4
BTC ETH SOL DOGE
Window
5 min
entry at 3:30-4:30

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.

1
Observe (0:00 - 3:30)
Price movement via Chainlink on-chain oracles on Polygon. Same source Polymarket uses to resolve.
2
Scan (3:30 - 4:30)
Evaluate 8-10 confidence factors. Only trade when confidence exceeds threshold.
3
Execute (4:30)
Buy UP or DOWN tokens via the CLOB API. FOK orders at best available price.
4
Resolve (5:00)
Market resolves. Winning tokens pay $1.00. Losing tokens go to $0.00.
5
Redeem
Auto-redeem winning CTF tokens to USDC.e, bridge back to pUSD. Funds recycled.

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.

Bot code
super-solid/integrator-research
experiments/23-polymarket-5m/bot.py
On the droplet
142.93.238.136
/root/integrator-research/experiments/23-polymarket-5m/
DJINN agent
super-solid/djinn
packages/djinn/
Auto-improver
integrator-watch.mjs
Hourly cron: research + build + review + merge
Observability
bot-observe.mjs
*/5 cron: probe + telemetry + alerts
Dashboard
djinn-site /trading
Live + Observability views
Trade log
decisions.jsonl
Every signal evaluation + order result
Bot logs
/tmp/polymarket-bot.log
Runtime output
VPN config
/etc/wireguard/wg-polymarket.conf
WireGuard to Japan (geo-bypass)
process flow
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).

F1Delta magnitude+0.20 weak (>0.05%) / +0.40 strong (>0.15%)How far price moved from open
F2Time remaining+0.30 (<60s) / +0.20 (<120s)Less time = more certainty
F3Market agreement+0.20 if Polymarket token price agreesMarket already pricing our direction
F4Trend consistency+0.10 if delta/range > 0.5Clean trend vs choppy noise
F5Orderbook depth+0.10 if bid/ask ratio > 0.6Liquidity supports our side
F6Volume+0.05 for high volumeMarket conviction
F7Spread-0.10 if spread > 0.98 (wide)Wide spread = illiquid
F8Asset history+0.10 based on EMA win ratePer-asset track record
F9Price velocity+0.00 to +0.10Slope of last 30-60s movement
F10Cross-asset+0.10 if 3/3 assets agreeMarket-wide directional consensus

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.

order flow
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.

Signer (EOA)
Phantom-derived Ethereum wallet
Signs orders
Deposit wallet
Smart contract on Polygon
Holds pUSD, places orders
pUSD
Polymarket-wrapped USDC
The only tradeable token
USDC.e
Bridged USDC on Polygon
Intermediate: wins redeem to this, then bridge to pUSD

The money loop

1
Trade
Bot places FOK order. $5.50 pUSD leaves deposit wallet.
2
Resolve
Market closes. If we won, we hold CTF tokens worth $1.00 each.
3
Redeem
Auto-redeem CTF tokens via the exchange contract. Returns USDC.e.
4
Bridge
USDC.e converted to pUSD via Polymarket bridge. Back in deposit wallet.
5
Recycle
pUSD available for the next trade. Self-sustaining.

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.

Collector

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.

probes
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)
Alerts

Slack alerts with per-condition cooldowns so you're never spammed.

criticalBot dead30 min
criticalVPN stale (>5 min)30 min
warningDashboard unreachable30 min
warningBalance below $502 hours
warningWin rate below 70%6 hours
warningDisk free < 2 GB2 hours
Frontend

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.

1
Detect
Poll integrator-research for new commits. Diff what changed.
2
Research
Spawn research.mjs with a question derived from the diff. 6-lens Research Skill Graph via Claude + WebSearch.
3
Build
Opus plans → Codex codes → one surgical improvement on the fork.
4
Review
review.mjs --gate: cross-model review. Codex fixes blockers (max 3 rounds).
5
Merge
If VERDICT: PASS with no blockers → auto-merge into integrator-work.
6
Flag
If still blocked after 3 rounds → Slack alert, human review needed.

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.

start (live mode)
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
monitor
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
crons (installed)
*/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.

commands
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.

Deposit wallets are mandatory

Polymarket requires signature_type=3 for all new API users. EOA mode (type=0) is rejected. Don't waste time.

Use polymarket-client, not py-clob-client alone

AsyncSecureClient.create() auto-deploys the deposit wallet and sets all exchange approvals gaslessly. py-clob-client can derive keys but can't deploy.

Session signer != your EOA

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.

pUSD, not USDC

The deposit wallet needs pUSD. Path: USDC -> USDC.e (Uniswap) -> pUSD (Polymarket bridge). CollateralOnramp wrap() does NOT accept native USDC.

Most signals won't fill

94.9% signal accuracy but only 75/497 orders matched. The bottleneck is CLOB liquidity, not prediction quality. Track both separately.

PostUp rule is life or death

Without the WireGuard PostUp rule routing SSH through eth0, VPN kills your connection to the droplet. Always keep it.

Phantom wallet surprises

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.

Late Window Certainty v1.0