Polymarket Crypto Insurance Bot: Arbitraging Prediction Markets with Real-Time Oracle Signals

March 25, 2026

Polymarket Crypto Insurance Bot: Arbitraging Prediction Markets with Real-Time Oracle Signals

Prediction markets don't misprice because participants are irrational. They misprice because information propagates unevenly.

Polymarket runs binary prediction markets on crypto price movements—will BTC be up or down from a starting price within 5 minutes? These markets resolve deterministically against Chainlink oracle feeds. And yet, in the final seconds before resolution, the market price frequently diverges from what the oracle already knows to be true.

This project started as an attempt to capture that edge: a single-process Go bot that fuses Chainlink price feeds, Binance market depth, and Polymarket's CLOB into a unified decision loop—then trades only when every signal agrees.


Why This Edge Exists

Polymarket's crypto prediction markets follow a simple structure. A market opens with a starting price—say BTC at $64,200. Participants buy YES (price will be at or above start) or NO (price will be below start). After 5 or 15 minutes, the market resolves against the Chainlink oracle price. Winners redeem at $1.00 per share.

The inefficiency isn't in the resolution mechanism—it's in the market's reaction time.

When Chainlink reports that BTC has moved up 0.15% from the start price with 90 seconds remaining, the YES outcome should trade near $0.99. But market makers are slow to adjust. Liquidity is thin. The CLOB lags the oracle by seconds that matter.

The bot doesn't predict where BTC is going. It identifies where BTC already is—and trades when the market hasn't caught up.


What This System Does (and Does Not) Do

What It Does

  • Ingests real-time price data from Chainlink, Binance, and Polymarket simultaneously
  • Computes 13 market signals per active window every 500ms
  • Applies a 6-gate sequential filter to reject bad trades before they happen
  • Executes fill-or-kill orders on Polymarket's CLOB when all conditions align
  • Manages positions through resolution, stop-loss, and redemption

What It Does Not Do

  • It does not predict price direction
  • It does not market-make or provide liquidity
  • It is not a high-frequency system competing on microseconds

This is a convergence bot—it waits for reality to be clear, then trades the lag.


Architecture: One Process, No Excuses

The entire system runs as a single Go binary. No microservices. No message queues. No Kubernetes.

Reliability over performance. Prefer "no trade" over a risky trade.

That constraint shaped every decision.

Chainlink WS ──┐
Binance Spot ───┤
Binance Perps ──┼──→ In-Memory StateStore (RWMutex) ──→ Decision Engine (500ms tick) ──→ Execution
Polymarket WS ──┤                                                                         
PM Tracker ─────┘                                                                    NDJSON  S3

Five connectors feed a single shared state store. One decision loop reads that state every 500 milliseconds. One execution path places orders. Everything else—logging, persistence, redemption—is async and non-blocking.

The entire hot state fits in roughly 10MB of memory.


Data Ingestion: Five Connectors, Five Realities

Each data source has its own failure modes, latency profile, and trust model. Treating them uniformly would be a mistake.

The ground truth. Chainlink feeds are what the market resolves against, so they define reality for this system.

  • WebSocket for push updates, REST as fallback for historical queries
  • Each price tick lands in a ring buffer (180 ticks, circular)
  • Staleness detection at 300ms—if the feed goes quiet, trading stops
  • The ring buffer drives volatility estimation via rolling standard deviation

Binance Spot

The confirmation signal. Binance is the deepest crypto spot market, so it serves as a sanity check against Chainlink.

  • 20-level order book depth at 100ms intervals
  • Aggregate trade stream for real-time flow analysis
  • Trade flow tracked in 300 one-second buckets (5-minute rolling window)
  • Buy/sell notional imbalance computed over 10s and 30s windows
  • Wall persistence detection for identifying sticky liquidity

Binance Perpetuals

The risk signal. Liquidation cascades on perps markets are the single most dangerous regime for this strategy.

  • Force-order stream tracks real-time liquidations
  • Mark price updates for reference pricing
  • A single boolean flag: LiquidationActive
  • If liquidations are firing, the bot does not trade

Polymarket Market WebSocket

The execution venue. Real-time CLOB data for both YES and NO outcomes of each active market.

  • Full order book snapshots on connect
  • Incremental updates: price changes, best bid/ask, trade executions
  • Dynamic subscription—assets added and removed as windows open and close
  • Custom events for market creation and resolution

Polymarket Tracker

The lifecycle manager. Sits above the market connector and manages the temporal dimension.

  • Queries the Gamma API for upcoming market windows
  • Pre-schedules subscriptions 10 seconds before a window opens
  • Snapshots Chainlink price at market start time
  • Tracks positions through trade, resolution, and redemption
  • Maintains the ActiveWindowSummary that the decision engine consumes

Every connector implements the same interface:

type Connector interface {
    Name() string
    Start(ctx context.Context) error
    Stop() error
    Health() error
}

Each runs as its own goroutine. Each handles its own reconnection. None are allowed to panic.


State Management: One Lock, One Truth

All five connectors write to a single StateStore protected by sync.RWMutex. The decision engine only reads.

This is deliberately simple. There are no channels between connectors and the engine, no event buses, no pub/sub layers. Just a mutex-guarded struct that represents the current state of the world.

AppState {
    Chainlink:     map[symbol]*ChainlinkState
    BinanceSpot:   map[symbol]*BinanceSpotState
    BinancePerps:  map[symbol]*BinancePerpsState
    Polymarket:    {OrderBooks, MarketAssets, ActiveOrders}
    Risk:          {FeedsHealthy, SymbolFreshness, TradingDisabled}
}

Updates happen through typed callback functions—UpdateChainlink, UpdateBinanceSpot—rather than raw field access. This keeps the mutation surface small and auditable.

The engine never calls the network. Every decision is made against local state. This makes the decision loop deterministic, testable, and fast.


The Decision Engine: 13 Signals, 6 Gates

Every 500ms, the engine snapshots the current state and evaluates each active market window.

Signal Computation

For each window, 13 signals are computed:

  • CLPrice — Current Chainlink oracle price
  • StartPrice — Price at window open
  • Gap — Absolute difference between current and start
  • WinningSide — YES if CL >= start, NO if CL < start
  • PMConfidence — Polymarket mid price for the winning outcome
  • BinanceMid — Binance spot mid price
  • DivergencePct — Percentage divergence between Binance and Chainlink
  • CLBinanceAgree — Do both feeds agree on direction relative to start?
  • LiquidityToFlip — Cumulative order book depth to the start price level
  • BookImbalance — Bid volume as a fraction of total near start price
  • ExpectedMove — Estimated volatility scaled by remaining time
  • LiquidationActive — Are perps liquidations firing?
  • TimeRemaining — Seconds until window closes

These signals are not combined into a single score. Each feeds a specific gate.

The 6-Gate Filter

The engine runs a sequential rejection pipeline. A trade is only placed if every gate passes. If any gate rejects, the reason is logged and the engine moves on.

Gate 1: Time Window Entry only in the final 30–150 seconds. Too early, and the market can still move. Too late, and liquidity dries up.

Gate 2: No Existing Position One position per market. No averaging down, no doubling up.

Gate 3: PM Confidence The market must already price the winning side at 95% or above. This confirms the market broadly agrees with the oracle—the bot is capturing the last few cents of convergence, not betting on direction.

Gate 4: Price Ceiling Maximum entry price of $0.97 per share. At $1.00 resolution, this guarantees a minimum 3% return if the position wins.

Gate 5: Chainlink-Binance Agreement Both feeds must agree on direction relative to the start price, with divergence under 0.05%. If the oracle and the deepest spot market disagree, something unusual is happening—and the bot steps aside.

Gate 6: Depth + Gap The price gap from start must exceed 3x the expected move (volatility-adjusted). The order book must have minimum liquidity depth. This filters out markets where the outcome is technically favorable but the margin of safety is too thin.

Gate 7: Liquidation Check If Binance perps show active liquidation cascades, no trade regardless of other signals.

The philosophy is simple: the cost of missing a good trade is low. The cost of taking a bad one is not.


Execution: Fill-or-Kill, Then Wait

When all gates pass, the engine constructs a market order:

MarketOrderRequest{
    TokenID:   winningAssetID,
    Side:      "BUY",
    Amount:    1.0,        // USD
    TickSize:  "0.01",
    OrderType: "FOK",      // fill-or-kill
    Price:     priceCeiling,
}

The order is sent to a Node.js execution service running on localhost. This service handles Polymarket's signing and submission protocol. The Go bot doesn't touch private keys directly.

Fill-or-kill means the order either fills completely or is cancelled. No partial fills, no hanging orders, no state to reconcile.

After execution, the position is tracked through resolution. When the market closes:

  1. The tracker queries Chainlink for the final price
  2. Determines the winning outcome
  3. Queues a redemption request (30-second delay to allow settlement)
  4. Polls for completion every 5 seconds, up to 2 minutes
  5. Retries up to 5 times on failure

Redemption is fully async. The decision engine never blocks on it.


Stop-Loss: Cutting Losses Without Panic

Not every trade wins. The stop-loss system handles losing positions with two triggers:

Book Recovery Trigger — Detects when the order book was empty (spread exceeds threshold), waits for it to return, and if the bid is below the stop price after 10+ seconds of emptiness, exits immediately.

Bid Persistence Trigger — If the bid stays below the stop price for 5 consecutive ticks, exits at the best available bid.

Both triggers use fill-and-kill sell orders that sweep available bids. Partial fills are handled gracefully—the position is updated, and if fully exited, redemption is queued.

The system does not chase. It does not widen stops. It accepts the loss and moves on.


Risk Management: Defense in Depth

Risk is not a single check—it's a posture.

Feed Staleness — Each data source has an independent staleness threshold. Chainlink: 300ms. Binance: 2 seconds. Polymarket: 2 seconds. If any feed goes stale, trading is disabled globally.

Cross-Feed Divergence — Chainlink and Binance must agree within 0.05%. Disagreement indicates either a feed issue or unusual market conditions. Either way, the bot waits.

Liquidation Awareness — Active perps liquidations suppress all trading. Cascades create unpredictable short-term price action that invalidates the convergence thesis.

Position Limits — Maximum 10 concurrent positions. Maximum 200 shares per trade. These are hard caps, not guidelines.

Daily Loss Limit — After 2 losing trades in a day, the engine pauses. This prevents compounding losses during adverse regimes.

Kill Switch — A configuration flag that disables all trading immediately. No code change required.


Persistence: Every Decision, Logged

Every event—every decision, every execution, every connector state change—is written to NDJSON files with hourly rotation and automatic S3 archival.

{
  "ts": "2026-03-25T14:22:01.453Z",
  "component": "engine",
  "event_type": "DECISION_EVENT",
  "msg": "gates_passed",
  "payload": {
    "slug": "btc-updown-5m-1711371600",
    "symbol": "BTC",
    "side": "YES",
    "cl_price": 64352.10,
    "start_price": 64200.00,
    "gap_pct": 0.0237,
    "pm_confidence": 0.97,
    "divergence_pct": 0.012
  }
}

This isn't just logging—it's an audit trail. Every rejected trade includes the gate that rejected it and the signal values at that moment. This makes post-hoc analysis straightforward: was the rejection correct? Would relaxing a threshold have been profitable or reckless?

Paper mode runs the same pipeline with the same logging, but records hypothetical trades at mid prices instead of placing real orders. The codebase doesn't branch—only the execution path differs.


Engineering Decisions Worth Noting

Ring Buffer for Volatility — Rather than storing unbounded price history, each Chainlink feed maintains a circular buffer of 180 ticks. Rolling standard deviation is computed over this window, giving a stable volatility estimate without memory growth.

Slug-Based Market Identity — Markets are identified by deterministic slugs: btc-updown-5m-1711371600. The prefix encodes the asset and window duration, the suffix is the Unix timestamp. This makes market discovery, deduplication, and pre-scheduling trivial.

Pre-Scheduled Subscriptions — The tracker loads market data 10 seconds before a window opens and subscribes to the CLOB immediately. This eliminates cold-start latency at the moment when data matters most.

Decoupled Decision Loop — The engine reads only local state. It never makes network calls. This means the decision path is deterministic and can be replayed against recorded state for backtesting.

Separate Execution Service — Private key management is isolated in a Node.js sidecar. The Go bot never touches signing material. A compromise of the bot process does not compromise funds.


What's Next

  • Expanded window coverage beyond 5 and 15-minute markets
  • Adaptive gate thresholds based on historical performance data
  • Multi-market correlation signals for portfolio-level risk
  • On-chain settlement monitoring for faster redemption
  • Backtesting framework driven by recorded NDJSON event streams

Final Thought

This bot doesn't try to be clever. It doesn't predict. It doesn't speculate.

It waits for moments where the oracle has already spoken, the spot market confirms, the order book has depth, liquidations are quiet, and the prediction market hasn't caught up. Then it trades the gap.

In prediction markets, the edge isn't in knowing what will happen. It's in recognizing what already has—and acting before the market agrees.