Whitepaper · version 0.2 · October 2026

Steel: measuring a market that never closes, and refusing to pretend

Abstract Steel is a single self-hosted process that listens to every print and every resting order on twenty-one crypto venues around the clock, forecasts each pair's one-hour return distribution on five-minute bars, and trains online as those forecasts resolve. This paper is organised around the two things that make it different from a trading terminal and from its equity sibling. First, it measures: the tape, the books, the derivatives feeds, the options surface and the calendar are stores with an API, and every number the dashboard shows names where it came from. Second, it refuses to pretend: a paper fill pays the venue's fee, the spread, funding by side, impact and the on-chain transfer's fee and wait; a short is an isolated-margin perpetual whose margin is the most it can lose; every map is labelled modeled or measured, and the modeled liquidation map scores itself against the venues' real forced orders; nothing is sent to a venue unless the operator armed it, and then only through a gate that defaults to a dry run. Each section opens with a measurement from a real run and its date. The honest findings so far: on the development box, small-size cross-venue arbitrage nets about nothing once bad quotes are excluded and on-chain inventory is charged; the forecasts' economic edge after costs is the open question the weekly backtest and the paper book exist to answer.

1. A market that never closes

measuredOn the reference box Steel has run through 2026-10-12 and 2026-10-13 without a session boundary: 288 bars a day per pair, budgets and limits turning over at 00:00 UTC, and a stall alarm that fired twice when a venue's socket died and no closed market excused it.

Flint's six principles stand: distributions rather than points, the clock as the only train/test split, one network for the cross-section, defined risk only, everything observable, and one machine that nothing leaves. Crypto adds a seventh, which this paper returns to repeatedly: the simulation pays every cost a real action would, including the costs that equities do not have. A paper book that skipped a withdrawal fee or a ten-minute settlement would flatter the policy on exactly the trades that fail in practice. And an eighth, which gives Section 8 its name: a map that is modeled says so, and says how well it has done.

Five differences from equities drive the rest of the design.

  1. No session. There is no close to train through, no overnight gap, no open to wait for. Budgets, limits and refreshes turn over at 00:00 UTC. A gap in the tape is never a holiday; it is an alarm.
  2. Many venues, one asset. The same coin trades on twenty exchanges with different quote currencies, fees, pair spellings, and occasionally a different coin under the same ticker. The data layer has to merge, rank and disambiguate before anything is modeled.
  3. Derivatives instead of borrowing. There is no stock loan. A short is a perpetual future with isolated margin, which bounds its loss by construction and pays or receives funding every hour or eight. A long can be the same instrument at 1x, which matters because spot retail fees are an order of magnitude higher than perp fees.
  4. Money moves on chains. Capital on one venue is not capital on another until a withdrawal clears and a deposit confirms: minutes for a stablecoin on a fast rail, most of an hour for bitcoin on its own chain, with a fee each way.
  5. The crowd is visible. Funding, open interest, liquidations, the long/short ratio, the basis and the options dealers' gamma are published by the venues themselves, which gives the policy a direct reading of positioning rather than a proxy.
21 venuestape, books, derivativesquote class, identity Bars5 min, 24/7real trades Features40 per pairz-scored SteelNetquantiles at 1 h, 4 h, 24 hup/down, gate, attention128-bar window Calibrationband scale stemperature T Policy, then the bookgates, vol target, clustersperps on two venues Online learnersteps per labela budget per UTC day weights Deep backfill, always oneach name from the firstvenue that pages it Bar storeone row per bar5% held out labels at t + 12, 48, 288
Figure 1. The bar loop, which never pauses. Hot boxes run on the accelerator. The one-hour label is the policy's; the four-hour and one-day labels train the extra horizons and come from the closes the bar store already holds.

2. Measuring the market

measured2026-10-13, a CPU box with six venues enabled for three core pairs: 3,400 prints a minute on the tape; 325 live order books from five venues; open interest of $18.2B in BTC across Binance, Bybit, OKX and Hyperliquid with an OI-weighted funding of +0.0069% per 8 h; 592 BTC contracts on Deribit giving a dealer gamma of +172M per 1% with zero gamma at 88,000 and max pain at 80,000; 159 builder-deployed markets on ten Hyperliquid dexes, counted and not modeled.

A trading terminal shows what the venues publish. Steel stores it, so that the model, the dashboard and other programs read the same thing, and so that every number can say where it came from. Six stores, each a module with an API route:

Each name is owned by the highest-priority enabled venue that lists it and is fresh; ownership fails over without operator action. Three venues are on out of the box (Coinbase, Kraken and Hyperliquid, the one perpetuals venue); the rest are present but off, because a box in a region a venue blocks, or one that wants two websockets rather than twenty, should start clean. Perpetuals venues rank below every spot venue for price, because a perp is a derivative that funding keeps near spot rather than spot itself. Two rules were learned on the first all-venue run. Quote class. Every venue declares whether its dollar pairs are quoted in USD, USDT or USDC; a standby venue may refresh a name's bid and ask only when its quote matches the active venue's, because a USDT book sat ten basis points above Coinbase's last trade and the policy read that basis as the cost of a fill. Identity. A ticker is not an asset: the same three letters named two different coins on two venues, and the dislocation monitor reported a 300-basis-point arbitrage between them. Steel keeps CoinGecko's top thousand by symbol and the names venues publish, marks a symbol with several ranked coins as ambiguous, and lets such a name pair across venues, or be served at all, only from venues resolved to the same coin. A third rule followed: the simulator never speaks for a name a live venue lists; when every live venue is stale the provider is nobody, so the stall alarm fires instead of a made-up price reaching the labels.

Bars are built only from real trades; a quote or heartbeat updates the last price and the spread but never forms a candle. The taker side comes from each venue's own convention, which differs enough to be worth a table in the source code: Coinbase reports the maker's side, Binance a flag that the buyer was the maker, most venues the taker directly. Stalls are alarms: with no closed market to excuse them, three bar intervals without a candle while prices tick means a trade stream died, and the dashboard and the alerts say so.

3. From measurement to features

measuredThe net reads 40 columns per pair per bar. On 2026-10-13 the feature set grew from 29 to 40 in four steps (the books, the flow, the options, the event clock); each step reset the net and kept the replay, the columns mapped by name.

Each name contributes 40 features per bar, standardized by a running mean and variance kept per name. Nine come from the bars; thirteen are exogenous signals joined from the signal loops and held between refreshes; four read the resting order book across venues (the imbalance within 0.5%, 2% and 10% of the mid and the depth within 1%); four are forced and confirmed flow (longs and shorts liquidated in the bar, new longs and new shorts the open-interest change confirms); two are the options surface for BTC and ETH (dealer gamma as a share of the chain and the at-the-money implied vol); one is the clock to the next scheduled high-impact event; seven are functions of the bar's timestamp, which matters in a market where the hour of day, the funding settlement clock and the CME session all shape the tape.

Table 1. The 40 features per pair per bar. A feature-set change resets the net but keeps the replay: the feature names travel with the store, columns are mapped by name, new ones start at zero and calendar ones are recomputed from each bar's timestamp.
GroupFeatureDefinition
Barsret, range, logvol, imbalance, spreadlog return, log high/low, log volume, (buy − sell) / volume from the taker side, bid-ask spread in bps
logtrades, closepos, mom, rvollog trade count, close position within the bar, the sum and the standard deviation of the last 12 bar returns
Attentionnews_sent, news_attnheadline tone from a local FinBERT over the crypto desks, rendered in a headless browser, and the name's share of coverage
reddit_sent, reddit_attnthe same from Reddit crypto chatter
Positioningfngthe Fear & Greed index
funding, oi_chg, longshort, basisthe perp funding rate, the change in open interest, the retail long/short ratio and the perp-spot basis, per name, from Bybit and Hyperliquid
dvolDeribit's DVOL, the crypto volatility index
Marketbtc_dom, breadth, stable_flowbitcoin dominance, advancers' share across the 250-name radar, the change in stablecoin supply
Calendarhour_sin, hour_cos, dow_sin, dow_cos, to_funding, cme_open, expirythe hour and the day of week as angles, time to the next funding settlement, whether the CME session is open, the Friday options expiry
Bookob_imb_05, ob_imb_2, ob_imb_10, ob_depth_1resting-book imbalance within 0.5%, 2% and 10% of the mid across the venues of one dollar class; log USD depth within 1%
Flowliq_long, liq_short, pos_long, pos_shortlog USD of longs and shorts liquidated in the bar on the perp venues; log USD of new longs and new shorts opened, as the open-interest change and the taker side confirm
Optionsgex_norm, iv_atmDeribit's dealer gamma as a share of the chain's notional and the nearest at-the-money implied vol (BTC and ETH pairs; zero elsewhere)
Eventsevent_hourshours to the next scheduled high-impact event (FOMC, CPI, the jobs report, a quarterly expiry) over 72, one for every pair

The net

SteelNet is FlintNet with a horizon axis. The same trunk, dilated causal convolutions over the 128-bar window per name with a per-name embedding, cross-asset attention across names, and a regime gate over expert heads, feeds H quantile sets and tail pairs instead of one. The first horizon, twelve bars, is the policy's; the extras, 48 and 288 bars, are forecasts the dashboard shows beside it and the policy consults as a veto. The loss weighs each extra horizon at half the policy's:

(1)L = Σh wh · (1/|Mh|) Σi∈Mh [ Στ ρτ(yi,h − qi,h,τ) + BCE(pi,hup, 1[yi,h > θ]) + BCE(pi,hdown, 1[yi,h < −θ]) ]

with w1 = 1 and wh = ½ for the extras. Why one trunk and not three models: the extra horizons are there to say whether the hour's call is a scalp into a trend, and a shared representation makes that comparison coherent rather than a disagreement between strangers. Only the first horizon is ever traded. Sizing to the machine is Flint's ladder, with one addition forced by the reference box: Steel shares its GPU with Flint, which holds about fifty gigabytes on weekdays, so the ladder runs under a memory floor and the start refuses, naming the other tenant, when the chosen rung would not fit beside it.

The replay and the universe

A market with no close produces 288 bars a day, every day, and the deep backfill reaches back months, so Steel stores one row per bar with its label, mask, timestamp and links to the bars before and after it, and gathers windows by index at batch time; a window is valid only over time-contiguous bars. Consecutive windows share all but one bar, so this holds sixty-four times the history Flint's window ring did for the same bytes: a year of four hundred names fits in under four gigabytes. The closes the store keeps also give the four-hour and one-day labels for free. Every twentieth bar by timestamp is held out of training for good as the one honest overfitting signal.

There are far fewer liquid crypto pairs than liquid stocks, so there is no reason to curate a small set. In the default liquid mode the universe is a sixty-name core, every holding in the operator's accounts, and every dollar pair the enabled spot venues list whose 24-hour dollar volume clears a floor (one million dollars a day, about 120 Coinbase names today), under a hard cap of 512 that is a limit rather than a target. Stablecoins and the fiat pairs Coinbase lists as dollar products are never modeled. Flint's grace and tenure rules apply, so a thin day never churns the set. The modeling floor is separate from the trading floor, which is twice as high.

4. Learning around the clock

measuredA restart on the Spark costs ten to thirty minutes of warmup; on the CPU box, about two and a half. Flint holds about 50 GB of the shared GPU on weekdays, so Steel's budget is 12 GB of replay and a 12 GB floor, tripled in training budget on nights and weekends when the handoff says the GPU is free.

Flint learns in four regimes separated by the session. Steel has no session, so the regimes are separated by budget and by who else wants the accelerator.

5. Trust

measuredTrust is a record, not a flag: at least 48 held-out calls over the last seven days hitting at 50% or better, then the live labels as they mature. The Model view shows the daily scorecard, calibration by pair, the drift of every feature over the last day, and the net's input weight on each feature as the L2 norm of its column in the first projection.

Calibration is Flint's: an online conformal scale on the predicted band widths that starts wide and earns its way down as the 10–90 band's coverage approaches 80%, and a temperature on the direction logits that starts sceptical. Trust is where Steel differs, because a restart on a market that never closes should not mean hours without a book. The held-out slice of the replay over the last seven days is scored the way live labels are, by direction hit on the calls the policy would have made, and the model is trusted when enough such calls hit; live labels take over as they mature. A scorecard that fails never revokes a trust the live record has earned. The per-name skill gate reads the same held-out calls on that name until live calls fill its record, then ages them out. The net's weight per feature is reported as what the net can read from a column, not what the column causes; a near-zero share says the column is ignored, which is the honest reading of a feature that was added yesterday.

6. Every cost is real

measuredA retail spot leg on Coinbase costs 40 bps; a perp leg on Hyperliquid under 5. A USDC transfer between them on the cheapest rail costs about a dollar and 5 minutes on Solana, 15 on Ethereum; bitcoin on its own chain, 45. The policy and the book charge all of it.

The policy is Flint's composition of auditable gates, each writing its reason into the name's card: the expected move against its uncertainty, the direction's margin over a coin flip, the edge net of every cost, the per-name record, the live spread, the daily dollar volume, the order's own impact by the square-root law and Amihud's illiquidity, the trend and the direction agreeing, and the same side persisting over several bars. Three gates are new: horizon agreement (a one-hour call whose four-hour median points the other way is held), volatility targeting (a position is sized to a daily volatility target over the name's realized daily volatility, floored at a quarter) and cluster caps (no sector carries more than 40% gross, and the book's net bitcoin beta stays within 1.0). The crowd overlay fades positioning that is visible here in a way it is not in equities; a thin edge into extreme crowding is flipped, and the overlay says so.

The paper book

No position may carry unlimited risk. A bearish view is an isolated-margin perpetual short at 2x: the margin posted, notional over leverage and capped as a share of equity, is the most it can lose, because the venue liquidates it at entry × (1 + (1 − maintenance) / leverage) and the book settles that at exactly the margin. A bullish view is, by default, a 1x perpetual long on the perp venue, whose worst case is the stake, the same as spot. There is no naked borrow and no leverage on the book as a whole. Funding accrues on every open perp at the venue's current rate, pro rata per bar: a positive rate pays the shorts and charges the longs.

Every cost a real fill pays

Fees are the venue's, not a flat number. A profile per venue holds its taker and maker rates for spot and perpetuals, its funding interval and its maintenance margin; two settings say which venue each side would trade on; and once a day each configured venue's reader fetches the account's actual fee tier and the book charges that instead. The policy charges two taker legs of the venue a side trades on, the funding a perp would pay while the rate runs against it (never a credit for funding it would earn), the spread, the impact and the Amihud charge against the median before it trades; the book charges the same on its fills. The consequence is deliberate: a retail spot tier never looks tradable at a one-hour horizon, so longs go to the perp venue.

Money between venues

The book keeps its cash on the spot venue and its collateral on the perp venue, split at the start. When a position needs more than its venue holds, the book sends the shortfall, as USDC on the cheapest rail in a table of withdrawal fees and settlement times per asset, charges the fee, and holds the money in flight for the rail's wait. The position does not open until the money lands, and only if the signal is still there when it does. The same table prices what the arbitrage executor refuses: a leg that would need a balance moved first is declined with the transfer's cost in basis points of the trade and its minutes, because a dislocation measured in seconds does not survive a ten-minute deposit. The chart draws every fill where it happened with its side and notional, the live mirror's slippage beside it, and each transfer as a band on the time axis from sent to landed.

Spot venue · cashspot longs (long_mode = spot)the index hedge's spot leg Perp venue · collateralperp longs at 1x, shorts at 2xisolated margin, funding USDC on the cheapest rail In flight: amount − fee, lands at t + waitcounted in equity, usable by neither venue the position waits; the signal must still be there when the money lands (solana 5 min, base 8, ethereum 15, bitcoin 45)
Figure 2. The two-pool book. The rail table holds typical 2026 fees and waits per asset and can be overridden per box; the cheapest honest rail is assumed, never Lightning unless the operator says both venues speak it.

7. Cross-venue structure

measured2026-10-11, one day, 21 venues: 140,935 episodes; the median lasted 171 ms; 42,137 were wider than 100 bps and one venue sat on 40,986 of them. Probed again on 2026-10-13, that venue's 86 overlapping books were all within 300 bps of the serving venue; the over-wide episodes predate the quote classes and the identity gate, and a gross gap past 500 bps is now refused as a wrong quote rather than counted as an episode.

With twenty venues quoting the same names, the manager's book hook delivers every venue's top of book, active or standby, to a monitor that opens an episode when the best bid across venues stands above the best ask across venues net of both venues' taker fees and a latency allowance, within the same dollar and the same kind, and closes it when the gap is gone. The episode's window, its peak and mean net edge and the top-of-book sizes go to a log and to the dashboard. The monitor measures; it never executes. Cross-stablecoin and spot-against-perp gaps are deliberately not episodes: those are a basis to carry, not a round trip.

An executor exists, off unless switched on and execution is enabled, that sends both legs at once when an episode's net edge clears a minimum, both venues are armed, both legs are the same kind, and the readers' last balances can fund them, flattening a leg whose partner failed. Before anything like that runs with money, the question is what the rules would have captured, and the log answers it. A replay applies the executor's rules by latency rung, from 50 ms to 3 s: an episode counts only when it outlasted the round trip, at its mean net edge rather than its peak, at the smaller of the book's size and the order cap, after a cooldown per name; and because each capture moves base to the buy venue and cash to the sell venue, every two orders' worth of one-way flow on a pair is charged an on-chain rebalance of two stablecoin transfers.

Table 2. One day of the dislocation log on the development box, 2026-10-11: 140,935 episodes across 21 venues, replayed with a $200 order cap, a 5 bps minimum edge, a 60 s cooldown and inventory rebalanced every $400 of one-way flow.
LatencyCapturedToo fastToo thinMean net bpsNet USDOf which transfers
50 ms2,16134,36039,87119.430.89−806.00
300 ms1,97462,01422,61019.86.76−775.00
1 s1,63181,33210,86021.017.56−667.00
3 s1,04091,1444,76222.936.12−440.00

Three things in that table are the finding. First, the over-wide episodes were a quote problem, not an opportunity, and the monitor now refuses them. Second, the median episode lasted 171 milliseconds; a human, or a process on the other side of the internet from the venues, sees under half of them. Third, the gross edge is real but small, about twenty basis points on a $200 order, and the inventory it leaves behind costs more to move back on-chain than the captures earn. At this size the honest number is zero. Larger size raises the edge faster than the rebalancing cost, and inventory pre-positioned on both venues removes the transfers, which is what an arbitrage desk is.

8. Honest maps

modeledThe liquidation map is built from open-interest changes and leverage bands, never from real positions, and scores itself: the share, by dollars, of the venues' real liquidations that fell within three price buckets of a modeled cluster of the same side as the map stood before each. On the first evening of the feed the score had no events to count; the label says so rather than showing a number.

The order-flow terminals draw two heatmaps that traders act on: where liquidity rests, and where leverage would be force-closed. The first is measured and the second is not, and the honest thing is to say so on the map itself. Steel's book heatmap is the mean resting dollars per price row per column from the level snapshots the venues' depth feeds showed; its label says what rested, not what would fill. Steel's liquidation map is a model: when open interest rises in a bar, the new notional is split by the taker side and projected to each leverage band's liquidation price from the bar's close; when open interest falls the clusters shrink in proportion; when price trades through a level the cluster is spent. No venue publishes aggregate liquidation levels, so the only check the map has is against the forced orders the venues do publish, which is what the calibration score reports, with its tolerance in basis points, beside the map wherever it appears. The options surface is measured from Deribit's marks and modeled through Black–Scholes; the dealer-gamma figure is labelled per 1% move, and max pain and zero gamma are what the chain implies, not where price will go.

The same rule applies to the model's own output. The forecast fan is a calibrated distribution with its coverage tracked; the calls drawn on the chart are filled when they hit and hollow when they missed; the paper book's fills and the live mirrors are drawn where they happened, with the slippage; a feature's weight is called what it is. A dashboard that only showed the hits would be a terminal, not an instrument.

9. From paper to live

measuredFifteen order clients exist behind one gate. On 2026-10-13 a TWAP of two children was started from a ticket on the CPU box with execution off: both children were refused by the gate before anything was signed, and the run says so. Nothing has been sent to a live matching engine.

Orders exist in one package, behind one gate, and every switch defaults to off. Execution must be enabled; a venue trades only when armed with a trade key of its own, separate from the read-only key; every cap (per order, per name, per venue, per day, exposure, leverage) must be set, because an unset cap is zero; the box clock must be within a second of the venues' server time; the kill switch, a flag, a button or a file, refuses everything; a reject starts a cooldown; and every request and answer goes to an audit log. Clients default to a dry run that signs and logs without sending. Balance readers are a separate package that can only read the endpoints each declares, and a test greps every path-like string in both packages for order, withdrawal and transfer words. The ticket is the operator's: editable, with its limit, stop and target as draggable rays on the chart, and a choice of sending it as one order or as a TWAP, a scale ladder or a chase; every child goes through the same gate.

Promotion is a checklist the operator reads, never a rule the code applies: the model trusted, the weekend backtest scored over enough days with a positive net and an adequate hit rate, the paper book running for enough days with an adequate Sharpe, drawdown and return. Once live, every paper fill becomes an order of a fraction of its notional through the client of the venue that side trades on, the book keeps what it expects to hold, reconciles it against the readers' positions on every poll, reports slippage against the paper price, and demotes itself at once on the kill switch, a drawdown past a limit or a drift past a tolerance. A stop cancels every open order; a panic button closes every position with reduce-only orders; a live book not heard from for half an hour comes back demoted. Alerts carry fills, calls, refusals, stalls, demotions and the kill switch to a phone, and the operator's own series alerts ride beside them.

Paper booktrusted model Checklistbacktest + paper record Operator promotesarmed venues, caps set Live, at 0.1xmirror, reconcile demoted at once on: kill switch · drawdown past the limit · position drift · dead-man · the panic button
Figure 3. The road from paper to live. Every arrow to the right is a deliberate act by a person; every arrow back is automatic.

10. Evaluation

measuredTwo GPU runs on 2026-10-12 reached trust and never traded: the live calibration had been poisoned by the simulator's bars, which is the origin of the rule in Section 2. The weekly backtest has not yet run on a replay free of that fault; the first clean weekend is the baseline.

Live scoring is Flint's: every forecast is scored an hour after it is made on direction, band coverage and pinball loss, per name and in aggregate, and the paper book's record is the primary evidence. The held-out scorecard is the second, recomputed on demand over the last seven days and written to a daily scorecard the Model view shows over time beside calibration by name, the drift of each standardized feature over the last day, and the net's weight on it. The walk-forward backtest is Flint's too, with days that are UTC days and a Sharpe that annualizes over 365, run weekly in the thinnest tape, Saturday 00:00 UTC, when it owns the GPU and Flint is off; the policy sweep over its stored forecasts proposes gates without applying them. Whether the forecasts carry economic edge after the costs of Section 6 is the open question the backtest exists to answer, and this paper will carry the number when there is one.

11. Limitations

12. Future work

  1. Pairs as a conversion graph. A pair is an edge between two assets on a venue; a rail is an edge between venues; a router prices the path, and the book holds assets rather than symbols. Most of the transfer and quote-class machinery in Sections 2 and 6 is this graph seen one edge at a time.
  2. Real exogenous history in the replay, so the thirteen signal columns are learned from months of data rather than from the live bars since the start.
  3. Impact on the paper fill, so the book pays what the policy already charges.
  4. Withdrawal fees and times from the venues' own endpoints, replacing the static rails.
  5. Funding as a target. The funding rate is forecastable and paid every hour or eight; a carry book that holds the basis with bounded risk is a second strategy the same data supports3.
  6. Meta-labeling over the outcome records the book already keeps, sizing by the probability that acting on a call pays after costs4.
  7. The tape's delta, the premium and the basis as feature columns, each a deliberate reset of the net, with a permutation test for what each contributes.

References

  1. I. Makarov and A. Schoar, "Trading and Arbitrage in Cryptocurrency Markets," Journal of Financial Economics 135(2), 2020. Article
  2. I. Gibbs and E. Candès, "Adaptive Conformal Inference Under Distribution Shift," NeurIPS, 2021. arXiv
  3. A. Schmeling, M. Schrimpf and K. Todorov, "Crypto Carry," BIS Working Paper 1087, 2023. Paper
  4. M. López de Prado, Advances in Financial Machine Learning, Wiley, 2018: triple-barrier labeling and meta-labeling.
  5. Y. Amihud, "Illiquidity and Stock Returns: Cross-section and Time-series Effects," Journal of Financial Markets 5(1), 2002.
  6. R. Almgren, C. Thum, E. Hauptmann and H. Li, "Direct Estimation of Equity Market Impact," Risk, 2005: the square-root law.
  7. R. Koenker and G. Bassett, "Regression Quantiles," Econometrica 46(1), 1978: the pinball loss.
  8. Flint: design of a continuously learning intraday market model, thingg, 2026. Whitepaper

Appendix: principal settings and the data API

Table 3. Every setting, generated from steel/config.py: 211 knobs, each STEEL_<NAME> in the environment; the 55 marked live change from the dashboard without a restart.
SectionSettingDefaultRole
Market datafeed"auto"auto | or a single source id to force
sources_on""comma list to force EXACTLY these on (plus sim); "+bitget,+kucoin" adds to the defaults
sources_off""comma list of venues to keep off even if on by default (Coinbase, Kraken, Hyperliquid)
coinbase_ws"wss://ws-feed.exchange.coinbase.com"
coinbase_rest"https://api.exchange.coinbase.com"
kraken_seconds20.0REST poll between websocket reconnects
bybit_seconds15.0seconds between Bybit ticker polls
okx_seconds15.0
coingecko_seconds60.0free tier: ~30 calls/min across everything
bar_seconds300.05-minute bars (every venue serves them natively)
backfill_days30.0history every name is paged before the first forecast (Coinbase serves 30 days for 60 names in about two minutes)
backfill_seconds0.0the same in seconds; 0 = backfill_days (set either; seconds wins when both are set)
backfill_pages0max REST pages per symbol (0 = whatever backfill_days needs)
train_epochs (live)1.0background training: up to this many passes over the replay per UTC day (0 = off); the market never closes, so this runs between bars
overfit_patience (live)3background training stops for the day when the held-out pinball (5% of windows never trained on) fails to improve over this many 40-step checks (0 = off)
deep_backfill_days (live)30.0keep paging history back this far and train on it (0 = off); the cap on market-data use
deep_chunk_days5.0days per deep-backfill page (5 days = 1,440 five-minute candles, 5 Coinbase requests per name)
deep_backfill_seconds150.0pause between pages, so a 60-name burst stays well under Coinbase's public limit
Problem definitionwindow64bars of context fed to the model
horizon12bars ahead (12 x 5min = 1h forecast): the policy's horizon
extra_horizons"48,288"more horizons the net forecasts alongside (4 h and 24 h in bars), shown, never traded; "" = none
quantiles(0.1, 0.25, 0.5, 0.75, 0.9)
The replay stores absolute labels either way, so this is a run-time switch, not a reset.target"absolute"
index_symbols"BTC-USD,ETH-USD""the market" for the relative target: modeled index names, first = the prior; unmodeled ones are ignored
hedge_index (live)onrelative target: hold, per index, the weight that cancels the book's loading on it
hedge_min (live)0.01an index hedge smaller than this share of the book is not worth its cost
beta_windows8640label rows the loadings are fit on (about 30 days of 5-minute bars)
beta_shrink400.0pseudo-observations behind the prior (1 on the first index, 0 on the rest)
Compute + auto-sizing (benchmarks the machine on first run and picks the biggestdevice"auto"auto | cpu | cuda | mps
compileofftorch.compile the net (CUDA only); falls back to eager on failure
auto_sizeon
autotune_util0.7fraction of the bar interval training may use
max_warmup_seconds180.0the autotune's go-live ceiling over warmup_steps: caps the auto model size
max_warmup_minutes30.0the most time the go-live warmup may take over the backfilled history (0 = no cap)
Model (overridden by auto-sizing unless auto_size is off)d_model48
dilations(1, 2, 4, 8, 16)
n_experts3
n_heads4
dropout0.15
Online learninglr (live)5e-4
weight_decay1e-2
label_smoothing0.1softens the P(up)/P(down) targets so the net cannot claim ~99% certainty
label_cap_bps3000.0cap on label magnitude in bps (0 = off); crypto moves 30% in an hour only on a broken feed
direction_threshold_bps10.0deadband: P(up)=P(ret>+t), P(down)=P(ret<-t); the gap is P(flat)
batch_size16
steps_per_label (live)2
replay_size8640replay capacity in bars when no replay_gb is set (8,640 = 30 days of 5-minute bars)
replay_gb0.0replay budget in GB; when set, the bar count follows the universe and replay_size is ignored
replay_days400.0the most history the replay keeps, whatever the budget allows (0 = no cap)
recent_frac0.3share of each batch drawn from the newest samples
recent_n256
warmup_steps60the least training on backfilled history before going live (the autotune's budget unit)
trust_days7.0the held-out bars of the last N days score the model before live labels can: trusted on history when they clear min_labels calls at min_hit_rate
warmup_epochs1.0passes over the backfilled history before the first forecast, with the held-out guard; 0 = warmup_steps only
input_noise0.1gaussian noise on standardized inputs during training
min_labels (live)48live (out-of-sample) labels before suggestions are counted
band_gamma0.05adaptive conformal step for the band scale
temper_lr0.01online temperature step for P(up)
torch_threads2overridden by autotune (up to half the cores on a GPU box)
Suggestion policyscore_threshold (live)0.35|q50| / IQR needed to act
prob_margin (live)0.06|P(up) - 0.5| needed to act
cost_bps (live)0.0a floor on the spread the policy charges (the live spread replaces it when wider); fees are separate, by venue
trade_venue_spot"coinbase"the venue a spot position would trade on: its fee profile (sources/fees.py) is what the policy and the book charge
trade_venue_perp"hyperliquid"the venue a perp (long or short) would trade on
long_mode"perp"perp: a bullish view is a 1x perp on trade_venue_perp (a stake-capped loss, perp fees and funding) | spot: it is spot on trade_venue_spot
long_leverage (live)1.0isolated margin per perp long = notional / leverage; 1x has no liquidation price and the stake is the worst case
spot_fee_bps (live)0.0taker fee per spot leg; 0 = the spot venue's profile
perp_fee_bps (live)0.0taker fee per perp leg; 0 = the perp venue's profile
max_size (live)1.0
move_floor_bps (live)8.0min |expected move| (bps) to take a side
Risk gates (policy only; the model is untouched). All live controls.skill_min_n (live)8matured direction calls a name needs before it may trade
confirm_bars (live)2the same side must persist this many bars before entry
min_hold_bars (live)6a spot position is kept at least this long unless the signal reverses
short_min_hold_bars (live)3hold a perp short at least this many bars before closing (anti-churn)
The cost edge beyond the quoted spread (steel/policy.py): square-root price impact of the order theimpact_eta (live)0.1square-root impact coefficient: cost = eta * sigma_daily * sqrt(Q / ADV)
max_impact_bps (live)10.0a name whose Amihud illiquidity says the order would move it more than this does not trade
funding_in_edge (live)oncharge the funding a short would pay (negative rates) against a bearish signal's edge
funding_interval_hours0.0the perp venue's funding interval; 0 = its profile (8 h on Bybit/Binance, 1 h on Hyperliquid). The rates Steel reads are Bybit's 8-hour unit, scaled to the interval
Exits on positions (0 = off) and a daily loss limit that stops new entries for the UTC day.stop_loss_bps (live)300.0close a position down this much from entry, min hold or not (crypto: wider than equities)
take_profit_bps (live)0.0close a position up this much from entry (0: the forecast decides)
max_hold_bars (live)0close a position held this many bars (0: the forecast decides)
exit_cooldown_bars (live)12after an exit the name waits this many bars before re-entering
horizon_agree (live)Truethe next horizon's median (4 h) must not point the other way before a 1 h call trades
vol_target (live)0.05daily volatility a full-size position is sized for: size scales by vol_target / the name's realized daily vol (floor 0.25x; 0 = off)
max_cluster_weight (live)0.4gross weight cap per sector (the market scan's categories); 0 = off
max_beta_exposure (live)1.0cap on the book's net BTC beta (sum of weight x loading on BTC-USD); 0 = off
daily_loss_limit (live)0.03fraction of the UTC day's opening equity lost before new entries stop for the day (0 = off)
max_spread_bps (live)20.0names quoting wider than this do not trade; the live spread also replaces cost_bps when larger
min_dollar_volume (live)2e6names trading less than this a day (from the bars held) do not trade: thin books, fake fills
size_by_coverage (live)onscale size by band coverage / 0.8, so an overconfident model trades small
short_leverage (live)2.0isolated margin per perp short = notional / leverage; the loss is capped at that margin (liquidation at +1/leverage)
short_max_frac (live)0.05margin per short, as a fraction of equity: the most one bearish view can lose
min_hit_rate (live)0.5only trade once directional skill (hit_ema) beats a coin flip
paper_min_trade_frac0.015skip paper rebalances smaller than this share of equity (anti-churn)
kelly_fraction (live)0.15fractional-Kelly sizing: weight = this * |q50|/IQR (risk-adjusted edge)
Signals + the crowd overlaymuted_symbols""symbols to watch-but-not-suggest (comma list)
signals_off""signal providers off by default: social, feargreed, derivatives, vol, market
signals_minutes (live)5.0how often to refresh exogenous signals
market_scan_seconds90.0light loop for the whole-market panel (CoinGecko's free tier is ~30 calls/min)
portfolio_seconds60.0how often to poll exchange account balances (read-only)
operator_half_life1800.0decay of an injected human note (seconds)
max_universe512hard cap on modeled symbols (Coinbase lists about 400 USD pairs; attention is cheap, the websockets are the limit)
universe_mode"liquid"liquid: every dollar pair the enabled spot venues list above model_dollar_volume (plus the core and holdings) | core: the configured core, holdings and the most interesting names (Flint's rule)
model_dollar_volume1e6the modeling floor in liquid mode: names trading less a day are not worth a row (the trading floor, min_dollar_volume, is separate)
dislocation_min_bps0.0an episode needs the cross-venue gap, net of both taker fees, above this
dislocation_latency_bps0.0a margin for the price moving while the orders travel
dislocation_max_gap_bps500.0a gross gap beyond this is a wrong quote (another asset or unit, a broken book), never an episode
dislocation_stale_s10.0a venue's quote older than this is not a leg
The GPU handoff with Flint (steel/handoff.py)handoffTruewatch for the peer and switch between shared and solo modes
handoff_peer"flint-run"the peer's systemd user unit
handoff_peer_process"python -m flint"
handoff_peer_port8000
handoff_schedule"Mon..Fri 19:45"when the peer's timer starts it (local time), for the lead-up release; blank = detection only
handoff_lead_minutes15.0release the GPU this long before the peer starts
solo_train_multiplier3.0train_epochs x this while the peer is off
solo_memory_floor_gb2.0memory_floor_gb while the peer is off
Order books (steel/book.py)booksTruesubscribe to each live venue's depth (Binance, Bybit, OKX, Kraken, Hyperliquid)
book_mb64.0memory for the decimated level history (the heatmap), split across the universe
book_history_s15.0how often a row of levels is kept per venue
book_levels40levels per side per venue kept in that row
Derivatives feeds (steel/derivs.py, steel/sources/derivs.py)derivsTrueliquidations, OI, funding and mark from the perp venues' public streams
derivs_venues"binance,bybit,okx,hyperliquid"which feeds run (ids binance-perp, bybit-perp, okx-perp; a bare venue name works)
liq_mb16.0memory for liquidation events, split across the universe
liq_alert_usd250000.0alert on a single liquidation at or above this notional (0 = off)
The trade tape (steel/tape.py)tape_mb256.0memory for every live venue's prints, split across the universe (about 8M prints)
tape_hours24.0how far back the tape answers
tape_alert_usd0.0alert on a single print at or above this notional (0 = off)
bubble_usd100000.0a print at or above this notional is a bubble on the chart
predictions_minutes10.0how often Polymarket's crypto markets are read (0 = off)
options_minutes10.0how often Deribit's BTC and ETH chains are refreshed for the GEX, smile and term structure (0 = off)
absorption_sensitivity50.010 (few flags) to 90 (many): how large new positioning must be, relative to recent bars, with price not moving
The operator token (steel/auth.py)auth"mutate"mutate: every POST needs the token (the default) | all: reads and the websocket too | off: nothing does
token""the operator token; empty = generated at first start, sealed in the vault, printed by `steel token`
api_weight_per_minute1200the /api/v1 weight budget per token per minute (steel/apiv1.py)
backup_dir""when set, the engine writes a `steel backup` archive there once a day
backup_keep14how many daily archives to keep there
backup_replayFalseinclude the replay buffer in the daily archive (large)
flatten_on_stopFalsea stop closes every live position (reduce-only market orders); open orders are always cancelled
live_deadman_minutes30.0a live book not heard from for this long at a start is demoted (and flattened when execution is on); 0 = off
clock_max_offset_s1.0box clock vs venue server time past which execution is refused
Execution (off by default; see steel/execution). Every cap at zero refuses every order.executionoffthe master switch: nothing is ever sent while it is off
execution_venues""comma list of armed venue ids; each needs its own <venue>-trade.json key
execution_testnetoffuse the venues' testnets where they have one
execution_dry_runonbuild, sign and log every order without sending it
exec_max_order_usd0.0
exec_max_name_usd0.0per name per UTC day
exec_max_venue_usd0.0per venue per UTC day
exec_max_day_usd0.0across everything per UTC day
exec_max_exposure_frac0.5open notional over live equity
exec_max_leverage2.0
exec_reject_cooldown_s300.0
The live book (steel/live.py): the paper book mirrored to the armed venues once promotedlive_scale0.1live notional per paper notional (the paper book is $100k)
live_min_backtest_days10
live_min_backtest_net_bps0.0net bps per scored day
live_min_hit0.52
live_min_paper_days7.0
live_min_paper_sharpe0.05per-bar Sharpe of the paper curve
live_max_drawdown0.05paper drawdown to be eligible; live drawdown from peak that demotes
live_drift_tol0.25sent vs held, as a share of the expected size, that demotes
The arbitrage executor (steel/arb.py): acts on the dislocation monitor's episodes, through the gatearboff
arb_min_edge_bps5.0net of both taker fees and the latency margin
arb_max_order_usd200.0per leg, or the top of book when smaller
arb_max_open3perp pairs held at once
arb_cooldown_s60.0per name between attempts
universe_refresh (live)ononce a day at the UTC boundary: the universe is rebuilt by its mode (a live control)
interesting_count (live)40how many "interesting" slots the universe keeps beyond core and holdings (a live control)
interesting_min_dollar_volume2e7a radar name needs this much daily dollar volume to be a candidate
interesting_grace_days14a promoted name is demoted after this many days without ranking in the top 1.5 x count
interesting_min_tenure_days5... and never before this many days in the universe
radar_top250movers watchlist size (CoinGecko serves 250 per page)
overlay_enabledonthe crowd overlay: margin of safety and fade-the-crowd on top of the raw call
overlay_aggr (live)0.70..1: how hard the overlay fades crowded trades
overlay_fade_at (live)0.45crowding magnitude above which aligned trades get faded
overlay_safety (live)0.5margin-of-safety penalty on asymmetric downside
News skimmernews_enabledon
news_minutes (live)3.0
news_scorer"finbert"headline tone: finbert (ProsusAI/finbert on the CPU, graded) or lexicon (word lists)
finbert_model"ProsusAI/finbert"
news_browseron
news_off""news source ids off by default: browser
news_sources"""Name|https://url,Name2|https://url2" to override the browser defaults
Narrative brief (a LOCAL model only; never a cloud API)ollama_host"http://localhost:11434"
brief_model"qwen3.5:latest"
brief_small_model"qwen3.5:latest"
brief_minutes15.0
brief_timeout1800.0
brief_enabledoff
brief_backend"ollama""ollama" or "openai" (llama-server / vLLM, local only)
brief_openai_base"http://localhost:8001/v1"
Server / UIhost"127.0.0.1"
port81008100: Flint owns 8000 on a box that runs both
tick_hz2.0
chart_bars160~13h at 5-min bars
paper_start100000.0starting cash for the paper-trading book
paper_perp_cash_frac0.5the share of the paper book that starts on the perp venue as collateral (the rest on the spot venue)
transfer_asset"USDC"the stablecoin cash moves between venues as (fees.RAILS)
min_transfer_usd250.0the smallest transfer the paper book makes between its venues
log_size60
Persistencestate_dir"state"
checkpoint_minutes5.0
replay_save_minutes30.0the replay buffer (tens of GB when full) is written at most this often, and at shutdown
backtest_weekly (live)onwalk-forward backtest over the replay once a week (a live control; action "backtest" runs one any time)
backtest_weekday (live)50 = Monday ... 6 = Sunday (UTC); Saturday is the quietest crypto day
backtest_hour (live)0UTC hour the weekly run may start
backtest_hours (live)12.0time budget for a run; the per-day training budget shrinks to fit it (0 = uncapped); live training waits meanwhile
backtest_night_epochs (live)1.0passes over the history seen so far per simulated day, as the background trainer does per day
backtest_days (live)0score only the last N days (0 = all timestamped history)
backtest_cost_bps (live)8.0round-trip cost per unit of position in the backtest (the live book charges the quoted spread)
backtest_warm_days7training-only days before scoring starts, like warmup
backtest_target (live)"both"absolute | relative | both (two runs, each on half the budget, reported side by side)
memory_floor_gb (live)10.0GB that must stay free: the backtest sizes its net to fit above it and stops below it; deep paging pauses near it (a live control)
Table 4. The feature columns, generated from steel/features.py and steel/clock.py: 40 per pair per bar, in the order the net reads them.
GroupColumnsFeaturesWhat they read
Micro9ret, range, logvol, imbalance, spread, logtrades, closepos, mom, rvol
Exogenous13news_sent, news_attn, social_sent, social_attn, fng, funding, oi_chg, longshort, basis, dvol, btc_dom, breadth, stable_flow
Book4ob_imb_05, ob_imb_2, ob_imb_10, ob_depth_1resting-book imbalance within 0.5%, 2%, 10% of mid; log USD depth within 1% (steel/book.py)
Flow4liq_long, liq_short, pos_long, pos_shortlog USD of longs and shorts liquidated in the bar; of new longs and new shorts opened (OI-confirmed) (steel/derivs.py, steel/positioning.py)
Options2gex_norm, iv_atmdealer gamma as a share of the chain's notional, the nearest ATM implied vol (BTC and ETH pairs; 0 elsewhere) (steel/options.py)
Events1event_hourshours to the next high-impact scheduled event over 72 (steel/calendar.py), same for every pair
Calendar7hour_sin, hour_cos, dow_sin, dow_cos, to_funding, cme_open, expiryfunctions of the bar's timestamp: the hour and the day of week as angles, time to the next funding settlement, whether the CME session is open, the Friday options expiry
Table 5. The data API, version 1, generated from steel/apiv1.py: 20 routes under /api/v1, each costing its weight against a sliding-minute budget; the operator token on every request when auth is on. Shapes in docs/api.md. The same stores answer steel mcp, whose tools read and never order.
RouteWeightReturns
GET /api/v10this index: the routes, their weights and the budget
GET /api/v1/usage0the weight used and left for this token in the current minute
GET /api/v1/markets1every modeled pair: base, serving venue, kind, price, 24-hour dollar volume, trusted
GET /api/v1/screener5one row per pair from every stored series (price, 1-hour change, tape delta, OI, funding, liquidations, book imbalance, dislocations, the model's call)
GET /api/v1/candles/{pair}?tf=300&n=300&venues=a,b&to=2OHLCV with buy/sell volume per candle from the tape (any tf for the day held) or the 5-minute bars beyond
GET /api/v1/tape/{pair}?n=50&min_usd=0&bars=602the last prints, size-band volumes over the hour, per-bar delta and CVD
GET /api/v1/delta/{pair}?bars=60&band=2per-bar buy and sell volume, delta and CVD; `band` selects one size band (0..10) or a `min_usd,max_usd` pair
GET /api/v1/footprint/{pair}?tf=300&n=120&bucket_bps=25bid/ask volume per price bucket per candle, POC, value area, imbalances, large prints
GET /api/v1/book/{pair}?n=16&bucket_bps=0&quote=2the aggregate ladder with venue shares, the depth bands, per-venue books
GET /api/v1/heatmap/{pair}?tf=300&n=120&bucket_bps=55resting liquidity per price row per column from the level history
GET /api/v1/liqmap/{pair}?n=288&bucket_bps=105the modeled liquidation map: clusters per leverage band, their evolution
GET /api/v1/derivs/{pair}?bars=722open interest, funding and mark per venue and aggregated; liquidations per bar and the recent list
GET /api/v1/positioning/{pair}?n=2883OI-confirmed net longs and shorts per bar, absorption flags
GET /api/v1/premium/{pair}?tf=300&n=288&reference=binance3per-venue premium to the reference venue and the perp-spot basis, per candle
GET /api/v1/levels/{pair}?days=352day, week and month opens, highs, lows and closes
GET /api/v1/options/{asset}2Deribit's chain: GEX by strike, smile, term structure, max pain, zero gamma
GET /api/v1/forecast/{pair}1the model's latest call: action, quantiles, P(up), size, the extra horizons
GET /api/v1/calendar1the next scheduled events and the event clock
GET /api/v1/predictions1Polymarket's open crypto markets
WS /api/v1/stream?pairs=&channels=0trades, book tops, liquidations, bars and rule hits as they happen