Files
btc-5m-edge-study/README.md
T
Michelle 4aa8789cba Initial commit — BTC 5-minute binary options edge study
Reconstructed strategy engine + execution layer, trained XGBoost models, a manual
trading tool, and the research writeup. Paper mode runs keyless over live WebSocket
feeds; live trading requires your own wallet. No secrets committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 22:49:05 -07:00

18 KiB
Raw Blame History

BTC 5-Minute Binary Options — An Edge Feasibility Study

Can a retail trader find a real, capturable edge in Polymarket's BTC up/down 5-minute binary options? I built a research system to answer that rigorously. The honest conclusion: almost certainly not — and the reason why is more interesting than a "yes" would have been.

A market-microstructure research project in Python / asyncio: it reconstructs a Chainlink-style reference from 8 exchanges over live WebSockets, runs a family of XGBoost-gated strategies, and executes on Polymarket's CLOB. AI-assisted with Claude Code.


About this repository

This is a reconstruction — a runnable extraction of the strategy engine, the XGBoost models, and the execution layer from a larger private system I built and operated. It is provided as-is and UNAUDITED.

  • Paper mode runs with no keys and reads live market data over WebSocket.
  • The execution layer places real orders with real funds if you supply wallet credentials in src/predictor/.env — none are included (.env.example ships blank).
  • If you intend to trade it: audit the code yourself and accept the risk. This is an engineering / research project, not financial advice.

Building it was AI-assisted with Claude Code: I designed the research questions, the market model, the strategy logic, and the architecture; Claude accelerated the implementation. The conclusions and the microstructure reasoning are mine.


The market

Polymarket issues a fresh BTC Up/Down binary option every 5 minutes. At the close of each window it settles by a simple rule:

If the Chainlink BTC/USD reference price at window close ≥ its value at the window openUP = $1.00, DOWN = $0.00. Otherwise → UP = $0.00, DOWN = $1.00.

Each side trades as a token priced between $0 and $1 on Polymarket's central limit order book (CLOB). The key fact that makes this analyzable:

A binary token's price is the market's implied probability of the outcome. UP trading at $0.60 means the market is pricing a 60% chance BTC ends the window up.

So the whole game collapses to one question:

Can I estimate the true probability of "UP" more accurately than the market's price implies — and act on it before the market reprices?

That splits into three investigations. The first two ask is there an edge? The third asks can you actually capture it?


Pillar 1 — Rebuilding the reference: 8 exchanges vs. the oracle

BTC does not have one price. It has a slightly different price on every exchange, updating at slightly different times — and settlement depends on the slowest of them all: the Chainlink oracle print.

So I rebuilt a Chainlink-style reference in real time by aggregating 8 exchanges, while separately tracking the majors that move first and carry the most weight — Coinbase, Binance, OKX:

  • WebSocket trade streams: Coinbase, Binance, OKX, Kraken, Bybit, Bitstamp
  • REST polls (1s — no free public trade WS): Gemini, CryptoCompare
  • Polymarket itself, over two WebSockets: the Chainlink price stream, and the CLOB order book (top 5 levels). REST is used only once per window, to discover the new market's token IDs.

The aggregation is deliberately not a naive average: sources stale by >2s are dropped, each venue gets a rolling bias correction, USDT-quoted venues (Binance/OKX/Bybit) are adjusted for the USDT premium (median USDT price median USD price), and the final reference is a lead-weighted mean with a freshness bonus for sources that ticked in the last 500ms.

The question: does the leadlag between venues create a timing edge? The measured hierarchy — the study's central empirical result:

1. The CLOB moves first      — market makers reprice the book ~45s before
                               Coinbase's own tape fully reflects the move
2. Coinbase & majors follow
3. The Chainlink oracle prints last — the settlement reference is the
                               slowest object in the room

There is a measurable leadlag. The catch: the fastest thing in the market is the order book itself — which means the people you'd be racing have already moved.


Pillar 2 — A directional signal from a rich parameter set

Beyond raw price, can BTC's behavior inside the window fold into a sharper directional estimate than the market's implied odds? The signal stack is deliberately wide — several families of parameters, not one:

  • Displacement & clock — oracle move since window open (pm_a), distance to strike, time remaining
  • Cross-venue edge — spot leaders vs. the oracle, both raw and EMA-smoothed (raw is faster; smoothed resists single-tick noise)
  • CLOB microstructure — mid, spread, ask velocity (how fast makers are repricing), one-sided buy-volume imbalance
  • Momentum & intensity — oracle-move acceleration, trade counts
  • ML layer — XGBoost models scoring late-window outcomes (top features: own ask level, ask margin, |oracle move|, trade count, move acceleration, ask velocity)

The core intuition — and the cleanest math in the study:

Late in the window, with a clear gap between spot and the strike and low remaining volatility, the outcome becomes highly predictable. To flip, the price would have to move a lot, fast, in the time left.

Modeling the price as a random walk to settlement, the probability of finishing on the current side of the strike is approximately:

P(UP) ≈ Φ(  gap  /  ( σ · √(time_remaining) )  )
  • gap = current spot the strike (the reference price at window open)
  • σ = short-horizon volatility
  • Φ = the normal CDF

Then the trade signal is simply the mispricing:

edge = P_estimate  P_market      # P_market = the token's current price

Trade only when edge is large and late in the window (small time_remaining), where the estimate is most reliable. That is exactly where a real edge, if one exists, would live.

Strategy families: taker and maker

The signals feed two kinds of strategies, mirroring the two ways to trade a book:

  • Taker (cross the spread when a signal fires): edge reversal — in the final seconds, a large cross-venue edge opposing the current leader → buy the reversal side; CLOB momentum — the book's ask rising fast means makers are repricing → follow them; CLOB volume — one side absorbing 5× the flow → follow it; early lock — oracle acceleration + volume doubling + book confirmation → buy the likely winner early, at ~0.67 instead of ~0.93, for 34× better payoff on the same thesis.
  • Maker (rest passive limit bids and let the market come to you): cheap trailing-side bids with a GTD expiry — a $1 "option" on a late flip — and counter-consensus resting bids that only fill if a stop-loss cascade briefly dislocates the book. No fill, no risk.

Exits run a ladder of aggression: post the ask → step to the bid → cross at market.

One economics finding worth stating plainly: low win-rate + cheap entry + high payoff beats high win-rate + expensive entry. When market makers price accurately, the "safe" side's profit is compressed to zero — a 96%-win-rate strategy can still lose money at the odds you're offered.


Machine learning — and what a weak model proved

To pressure-test whether the directional signal was more than eyeballed thresholds, I trained gradient-boosted trees (XGBoost) as the ML layer. Two model families (all shipped, trained, and loadable in models/):

  • Outcome / "lock" model — 15 pre-trigger features (ask, abs_pm_a, left, rv_5m, rv_15m, ask_margin, pm_a_accel, ask_vel, vol_ratio, total_vol, sign_changes, …). Label: does the current leader hold to settlement? Used as a gate — only take the trade if the model's confidence clears a threshold.
  • Direction models — a separate 32-feature model per time-to-close slice (models at 250s, 200s, …, 10s remaining), because late-window dynamics differ from early. Features span both the asset (cb_a, bn_a, edge, rv_5m, funding, oi_chg, pm_a_z) and the counterparty (bid_ratio, ask_from_peak, vol_ratio, total_vol).

How I framed it, and read it honestly: the label is the settlement outcome, the metric is AUC, and the result was deliberately unimpressive — direction AUC ≈ 0.666 (0.50 is a coin flip; 0.70 is where a signal starts being useful). The lock gate had to be lowered from 0.90 to 0.75 before it would release enough trades to matter, and the lock strategy it gated still finished at a 96% win rate but negative PnL — accurate predictions, wrong odds.

That weakness is the finding, not a failure. Two things fall out of the feature importances:

The most predictive features are the market maker's own quoteask, ask_margin, ask_velocity — not BTC's price dynamics. The model taught itself that the single best estimate of the outcome is what the maker is already charging. You cannot beat a signal that is the counterparty. A near-random AUC on BTC-derived features is direct evidence that the exploitable information isn't in the price — it's already priced.

See it yourself: python ml/xgb_eval.py loads the trained models and prints their feature importances (the maker's quote on top) plus the per-entry-price economics from real paper trades (every price bucket is net-negative).

What I'd do next (unexplored): every model here predicts the outcome. The open direction is to change the label — model the counterparty's next action (will the maker pull the quote, widen the spread, shade the book?) rather than the asset. That reframes the problem from out-predicting BTC (a race you lose to better infrastructure) to detecting maker behavior (where retail-observable microstructure might actually carry signal). The codebase has a seed of this — a maker_bias strategy built on "the maker knows the outcome, follow their bias" — but a proper counterparty model is the study's most interesting untested hypothesis.


Pillar 3 — Execution: a faster manual trading interface

A correct signal is worthless if you can't act on it in time. Polymarket's native UI is too slow to place an order inside the seconds when an edge exists — and it's poor for building intuition about how the book behaves under time pressure.

So I built a hotkey terminal interface over the CLOB (tools/quick_trade.py): raw-keyboard mode where a single keystroke fires an order (buy up/down at the ask, sell at the bid, one-cent "lottery" limit bids), order parameters pre-warmed and cached so submission skips per-order lookups, prices streamed from a local feed instead of fetched per click — and every order prints its measured latency in milliseconds. Full product write-up: docs/QUICK_TRADE_PRD.md.

But building it surfaced the deeper execution problem — and it isn't clicking fast. You don't buy; you post an order and hope. Every trade is a little state machine:

quote (best ask) → post → { full fill | partial fill | no fill } → retry / cancel

On a thin binary book, partial fills and no-fills are the norm, not the exception. Each retry chases an ask that is already moving — the maker reprices the instant you show intent — so you either cross to a worse level (slippage, straight out of a margin that was pennies to begin with) or you miss and the window closes. Exits face the same gauntlet as a ladder of escalating aggression (post the ask → step to the bid → cross at market), with a circuit breaker that halts trading when round-trip latency spikes. That loop — quote → partial → retry → fill — routinely takes longer than the edge itself exists.

This pillar is where the thesis breaks: the latency inside the fill lifecycle, plus the slippage from thin liquidity, eats essentially the entire edge. A signal that's correct and a UI that's instant still lose money at the point of the fill.


Operating reality — what actually broke

Running this against live funds is where the thesis stopped being theoretical. These failures are first-hand — and they are why an edge that looks real on paper doesn't survive contact with the chain:

  • Latency — the round trip from signal to acknowledged order regularly outran the seconds an edge exists.
  • Quoted vs. filled — the ask you decide on and the price you actually pay diverge, worst during the fast moves where the signal fires.
  • Can't buy / can't sell — thin books mean orders partially fill or don't fill at all; exiting a position under time pressure is not guaranteed.
  • Opening-balance desync — on-chain settled state and the bot's local view drift apart (a redemption not yet reflected, a fill counted twice), and a wrong balance quietly corrupts the next decision.
  • Fragile to change — every strategy tweak surfaced a new on-chain edge case: nonce/gas, order expiry, reprice races, partial-fill accounting — none of it visible in backtest.

None of these are bugs to be fixed away; they are the structural tax of executing at retail on a chain-settled venue. Together they are the concrete form of the conclusion: the math can find an edge; the execution layer eats it.


The honest conclusion: retail cannot win this

Even when the analysis surfaces a theoretical edge, retail can't capture it.

  • The market makers consume the same exchange feeds this system does — and reprice the book in milliseconds. By the time a retail-latency system, let alone a human on hotkeys, detects the move and submits an order, the ask has already moved. The signal hierarchy in Pillar 1 is the proof: the book is faster than the exchanges it's pricing.
  • The directional edge from Pillar 2 is real but thin, and it is exactly what the market makers are also computing, faster, with better infrastructure. Watching maker behavior over several weeks of tick data made this vivid: for a stretch, makers were slow to reprice late-window reversals — and within about three weeks they had adapted and that edge collapsed. The market learns.
  • Pillar 3 and the operating reality above make the ceiling concrete: even a heavily optimized execution path is too slow, and the fill lifecycle plus thin-book slippage consumes what little edge survives.

The math can find the edge. Market structure makes it uncapturable at retail speed and cost. Compute, co-location, and latency consume the entire edge before it reaches you. This is structurally an institutional game — which is the single most useful finding of the study.

For anyone building consumer-facing trading products, that's the lesson worth carrying: on any market where the edge is faster than the retail user, the honest product isn't "help them trade the edge" — it's designing for the game retail can actually win.


What's in the repo, and how to run it

tools/chainlink_predictor.py   the live engine — reconstructs the reference, runs the
                               strategies, prints signals + (paper) fills
tools/quick_trade.py           hotkey MANUAL trading terminal (live only) — see the PRD
src/predictor/                 the real engine: strategy.py (the ~4k-line core), collector,
                               feeds, orderbook, pnl — and the live executor
ml/                            XGBoost: offline model inspection, training scripts, a sample
models/                        the trained XGBoost models (lock + per-slice direction + hold7s)
docs/QUICK_TRADE_PRD.md        product write-up of the manual trading tool

Requires Python 3.10+ (3.13 recommended). Set up once:

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
What Command Wallet keys?
Strategy engine, paper — reads live data, simulates fills, prints records python tools/chainlink_predictor.py --shares 5 no
Inspect the trained XGBoost models (offline, instant) python ml/xgb_eval.py no
Strategy engine, live — real automated orders python tools/chainlink_predictor.py --trade --shares 5 yes
Manual trading terminal — real orders (run the engine in another terminal for prices) python tools/quick_trade.py yes

Paper mode connects to Polymarket + 8 exchange WebSockets and needs nothing but internet and the market being open. (Binance blocks some regions with HTTP 451 — that source drops out and the reference uses the rest; non-fatal.)

Live trading needs your own wallet in src/predictor/.env (copy .env.example and fill it in) and a Polymarket CLOB client. The live path uses a client fork that is not on PyPI, so live execution isn't reproducible out of the box; paper is. No keys ship with this repo. Re-read About this repository before trading anything.


System architecture

  Coinbase ── WS ──┐
  Binance ─── WS ──┤
  OKX ──────── WS ──┤  8-exchange reference
  Kraken ───── WS ──┼  (staleness-filtered,
  Bybit ────── WS ──┤   bias-corrected,        ┌─►  Strategy  ─►  Executor
  Bitstamp ─── WS ──┤   lead-weighted mean) ──►│   (P_estimate,   (risk gate, fills,
  Gemini ──── REST ─┤                          │    edge, maker    paper PnL, or live
  CryptoCompare REST┘                          │    + taker        orders on the CLOB)
  Polymarket ─ WS (Chainlink price) ──►  Merger ┘    signals)
  Polymarket ─ WS (CLOB order book) ──►  (one clock for many feeds)
  • Feeds — independent async clients per venue (WebSocket where available, 1s REST polls otherwise); each normalizes into a common tick model.
  • Collector / Merger — combines multi-source ticks into a single time-ordered market state (one clock for many feeds) and reconstructs the reference.
  • Strategy — computes the reference, the gap, P_estimate, and the edge; runs the taker/maker strategies and the XGBoost gates; emits a signal only inside the gate (late window, edge threshold).
  • Executor — simulates fills for paper mode, or (with --trade and your keys) signs and posts real orders to the CLOB and settles on-chain.

Stack & authorship

Python · asyncio · aiohttp · numpy · XGBoost · web3 / py-clob-client (live path). Built AI-assisted with Claude Code: I designed the research questions, the market model, the strategy logic, and the architecture; Claude accelerated the implementation. The conclusions and the microstructure reasoning are mine. This public repo is a reconstruction of a larger private system I built and operated.