Skip to content
All writing

Interactive appendix2026Published3 min

HFT feature explorer: one recorded 15-minute SOL market

Fifteen minutes of Binance and Polymarket data from my HFT recorder, with feed freshness and derived features on one clock. Run the same kind of inspection that sat behind the HFT datasets.

Market
SOL 15-minute Up/Down on Polymarket (sol_1771810200)
Recorded
23 Feb 2026, 01:30–01:45 UTC
Data
58,613 rows × 57 columns; a 12,000-row sample is embedded
Format
Single HTML file (Parquet export + Plotly.js)
  • Polymarket
  • Plotly
  • Time series
  • Feature engineering
  • Data quality
  • Market microstructure

Interactive

Fifteen minutes of one market, on the recorder's clock

The explorer opens on the pair behind the HFT thesis that Polymarket might lag its reference market: the Binance SOL ask (binance_ask, left axis) and the Polymarket bid for Up (up_bid, right axis). The x axis is local receipt time, when my machine got each update: the clock a live strategy actually has.

Recorded L1 explorer for Polymarket slug sol_1771810200

Open full screen
Line chart of the Binance SOL ask on the left axis and the Polymarket bid for Up on the right axis across the 15-minute window, the explorer's default view

Loads about 6 MB of data and Plotly.js from its CDN. Best on a desktop screen.

Guide

Five checks to run before trusting a feature

I used the explorer for these questions during the HFT work. Control names match the explorer's labels.

  1. Do the two markets move together?

    Series A binance_ask · Series B up_bid

    If Polymarket lagged Binance, a move in the ask would reach the Up bid a moment later. Drag across the chart to zoom in; Reset Plotly zoom undoes it.

  2. How stale is each quote?

    Series B poly_age_ms · Series C binance_age_ms

    Every row records how old the latest update from each feed was. That turns misalignment into data, and it let training rows be filtered by latency thresholds such as 200–500 ms.

  3. Does smoothing help or hurt?

    Hampel → EWMA → rolling median

    Load a noisy series such as order_book_imbalance and add the filters one at a time; they always run in this order. Hampel and EWMA only look back, as a live model must. The rolling median is centered and also uses later rows: fine for the eye, not for a live feature.

  4. Do two features share a shape?

    Normalize (z-score) · Force single left Y axis

    Normalize turns each series into z-scores over the visible window, so prices, sizes and returns can share one axis. Try order_book_imbalance against return_1s: one HFT hypothesis was that imbalance and short lag features carried signal.

  5. Does the last minute behave differently?

    Full X range · Last 10% of X · Last 60s

    The view opens 10 s in; Full X range shows all 900 s. Last 60s isolates the final minute, often trivial or structurally unrealistic in a 15-minute market, and a source of fake signal if it leaks into a backtest.

Legend

Quotes, clocks, features and one label: the 57 columns

The dropdowns sort the columns alphabetically. By role, they fall into six groups.

Binance reference market (9)
SOL top of book: binance_bid, binance_ask, their _size columns, binance_spread, binance_spread_bps and log_binance_ask_size, plus underlying_price and the binance_connected flag.
Polymarket book, Up and Down (17)
Bid, ask, mid, spread and sizes for both outcomes (up_*, dn_*, log_up_ask_size), the VWAP of a $1,000 order on each side (vwap_*_1000usd) and slippage_ask_1000usd_bps. The Down book largely mirrors Up (dn_ask ≈ 1 − up_bid), so read them as one signal, not two.
Clocks and freshness (8)
When my machine received the row (local_receipt_ts_ms, ts_datetime), each feed's own timestamp (binance_ts_ms, up_ts_ms, dn_ts_ms), and how old or delayed each update was (binance_age_ms, poly_age_ms, network_latency_ms). Staleness is a column, not an assumption.
Returns, volatility, imbalance (10)
return_100ms, return_1s, return_5s and z-scored versions of the first two; volatility over 30 s, 5 min and 15 min (vol_30s, vol_5m, vol_15m); order_book_imbalance and jump_flag.
Contract and time to expiry (12)
strike_at_open, strike_proxy, distance_to_strike, moneyness and two variants, seconds_left, ms_left, log_time, nonlinear_time, interval_start_ts and fair_prob_up, a fair-value probability for Up.
Hindsight (1)
close_at_15m is the price at the end of the window, repeated on every row. It is only known at expiry, so it can label a row but must never feed a feature.

Context

Where this slug sits in the HFT pipeline

The HFT work was not just about training LightGBM on a prepared table. A big part of it was checking recorded data like this.

  1. 01

    Record

    Rust hot path

    • Binance and Polymarket WebSockets
    • local_receipt_ts_ms on every tick
  2. 02

    Store

    Python ETL

    • JSONL archive
    • Incremental conversion to Parquet
  3. 03

    Prepare

    prepared_l1 and prepared_l2

    • Train, val and test sets
    • Latency filters such as 200–500 ms
    • Leakage checks
  4. 04

    Model

    LightGBM

    • Targets such as hft_1s, maker_1s and adverse_bid_1s
    • adverse_bid_1s: about 0.921 test accuracy, still negative maker-style PnL

The explorer reads one Parquet L1 slug from the Store stage.

Recorded 15-minute SOL window: the Binance ask (blue, left axis, 78.4 to 78.9) plotted against the Polymarket Up bid (orange, right axis, 0 to 0.9) over 900 seconds

Market systems2025–2026Stopped

Polymarket trading systems

Prediction-market research: a dataset builder, a 15-minute bot and a Rust + Python HFT recorder.

Get in touch

Working on something similar? I am happy to compare notes.