Market Data
Everything you need to see the market — NBBO and book snapshots, balance + position views, fees, pre-trade previews, history, funding rates, and prediction-market discovery. Most of these endpoints are cold-resilient by default: the first call on an unwarmed symbol falls back through dynamic-subscribe → edge-routed REST snapshot → venue public API, so you don't need an explicit warm step before trading.
Quote — single symbol
NBBO + per-venue BBO + merged book. The one call every strategy starts with.
let q = seq.quote("BTC-USD").await?;
println!("mid={:.2} spread={}bps source={}",
q.nbbo.mid, q.nbbo.spread_bps, q.source);Returns Quote { symbol, nbbo, venues: HashMap<_, QuoteVenue>, book: Option<QuoteBook>, source }.
The source field
Every quote tells you which path served it. Trading code must gate on source == "edge_nbbo" for live-WS guarantees; research can accept any non-unavailable source.
| Value | Meaning |
|---|---|
edge_nbbo | Cache hit — live WebSocket NBBO, sub-ms |
edge_rpc_snapshot | Cold-path REST through the edge — 100–300ms, also kicks off background WS subscribe |
venue_public_api | Graceful-degradation fallback — edge RPC unreachable |
unavailable | No path produced book data (market empty, venue 404'd) |
Skipping the cold-subscribe wait
For research-grade scans where you don't care if the first call returns zeros on cold symbols, skip the up-to-2s wait:
let q = seq.quote("BTC-USD").nowait(true).send().await?;Trading code must never pass nowait=true — first-call zeros on cold tickers will silently misroute orders.
Quote — batch (50+ symbols)
POST /v1/quotes_batch does server-side fan-out with bounded concurrency. Per-symbol errors return as partials so one bad ticker can't fail the whole batch.
let r = seq.quotes_batch(&[
"BTC-USD",
"4394372887385518214471608448209527405727552777602031099972143344338178308080",
"KXNBA-26-TOR",
])
.depth(20)
.nowait(true) // recommended for batches of 50+
.concurrency(64) // cap 256
.send()
.await?;
for entry in r.quotes {
match entry {
BatchQuoteEntry::Ok(q) => { /* q.source, q.nbbo, q.book */ }
BatchQuoteEntry::Err { symbol, error } => eprintln!("{symbol}: {error}"),
}
}| Param | Default | Notes |
|---|---|---|
depth | 10 | Book levels per side (1–100) |
nowait | true | Default true — serial 2s waits are catastrophic at batch scale |
concurrency | 64 | Max concurrent upstream fetches (cap 256) |
instrument_type | auto | Override "spot" / "perp" / "prediction" |
Positions & Balances (unified)
One endpoint, one shape, across fiat, crypto, perps, and event contracts. Fills come from three reconciled sources (fills → write path, venue balance APIs, venue portfolio APIs).
use sequence_sdk::InstrumentKind;
// All active positions across every venue
let resp = seq.positions_unified().fetch().await?;
println!("NAV = ${:.2}", resp.totals.nav_usd_1e9 as f64 / 1e9);
// Cash-only (fiat + crypto stablecoins)
let cash = seq.positions_unified().cash_only().fetch().await?;
// Event contracts on Kalshi + Polymarket
let events = seq.positions_unified()
.events_only()
.venue("kalshi")
.venue("polymarket")
.fetch().await?;Builder methods: .venue(s), .kind(k), .cash_only(), .events_only(), .include_closed(b).
Each PositionView carries qty_1e8, entry_price_usd_1e9, current_price_usd_1e9, unrealized_pnl_usd_1e9, realized_pnl_usd_1e9, fees_usd_1e9, lifecycle, settled: bool, settled_mark_usd_1e9: Option<i64>. settled flips to true the instant resolution telemetry lands — check it to render post-settlement P&L without waiting for the venue to zero out qty.
instrument.kind is a tagged union:
kind | Additional fields |
|---|---|
"fiat" | code (e.g. "USD") |
"crypto" | symbol (e.g. "BTC", "USDC") |
"perp" | symbol, base |
"event_contract" | market_slug, outcome, token_id?, expiry_ts? |
The legacy balances(), positions(), perp_positions(), and portfolio() methods are deprecated and point at endpoints that now return 404. Migrate to positions_unified() before the next major.
Fees
Maker/taker fees per venue for a ticker. null taker/maker means "do not route here," not "free."
let resp = seq.fees("KXMLBHR-26APR292140KCATH-ATHLBUTLER4-1:YES")
.venues(&["kalshi", "polymarket"])
.fetch().await?;
for row in &resp.fees {
println!("{} taker={:?} maker={:?}",
row.venue, row.taker_bps, row.maker_bps);
}/v1/fees returns the fee components and curve formula (fee_model, components.rate_bps, formula.taker_fee_usd) so latency-sensitive callers can compute the exact realized fee locally — fetch once per ticker, cache, evaluate per fill. For a server-authoritative answer that also includes size-aware slippage and total landed cost, use preview() below.
Pre-trade Preview
Estimated fee + slippage + total cost + historical time-to-first-fill across candidate venues, without submitting.
use sequence_sdk::Side;
let p = seq.preview("ETH-USD", Side::Buy, 50.0).await?;
println!("best_venue={} cost_bps={}",
p.best_venue, p.estimated_total_cost_bps);Returns OrderPreview { candidate_venues, best_venue, estimated_fee_bps, worst_case_fee_bps, estimated_slippage_bps, estimated_total_cost_bps, historical_ttff, nbbo, source, generated_at_ns, … }.
Cold-resilient by default. First call on a cold ticker takes ~200–500ms and returns real fee + slippage + NBBO. NBBO fields (bid_px_1e9, ask_px_1e9, mid_px_1e9, spread_bps) are nullable — one-sided books return None on the missing side rather than serializing sentinel values. The same cold-fallback applies to /v1/intelligence/depth, /v1/intelligence/slippage, /v1/intelligence/routing, and /v1/execution/forecast — no explicit quote() warm step needed.
Price History
Unified history primitive across CEX, perp, DEX, Kalshi, and Polymarket. Bar mode (default) or tick mode (fidelity_secs=0).
use sequence_sdk::builders::history::Range;
// Year of Binance BTC-USDT 5-min bars
let btc = seq.price_history("BTC-USDT")
.range(Range::OneYear).fidelity_secs(300).venue("binance")
.fetch().await?;
// Last 500 tape prints with qty + side
let ticks = seq.price_history("BTC-USDC").ticks(500).fetch().await?;
// Polymarket 5-min lifecycle, include Chainlink underlying
let hist = seq.price_history("btc-updown-5m-1776537600")
.fidelity_secs(0).limit(50_000).underlying()
.fetch().await?;
if let Some(u) = hist.underlying {
if u.agrees_with_settlement == Some(false) { /* drop for ML */ }
}Builder methods: .range(Range), .fidelity_secs(secs), .venue(v), .ticks(n), .start_ts(s) / .end_ts(s), .paginated(), .underlying(), .fetch().
Key points:
fidelity_secs=0switches to tick mode — points carryqty_1e8,side,venue_id.venueis required for long-range (>24h) CEX/perp/DEX queries; ignored for Polymarket / Kalshi / short-range.paginate=true(Polymarket-only) walks back in 7-day windows to bypass the venue's ~5k-point per-call cap.underlying=trueincludes the Chainlink/Binance underlying curve for Polymarket crypto updowns;agrees_with_settlement == falseis a signal to drop for ML training.
Funding Rates
Perpetual funding rates, normalized to bps_per_hour so Binance (8h cadence) and Hyperliquid (continuous) are directly comparable.
let all = seq.funding_rates().await?;
let one = seq.funding_rate("hyperliquid", "ETH-USD-PERP").await?;
let venue = seq.funding_rates_for_venue("binance").await?;Each FundingRate carries rate_bps_per_hour, predicted_rate_bps_per_hour, mark_price_1e9, open_interest_1e8, next_settlement_ns, snapshot_age_ms.
funding_history returns a signed rate_1e9 per settlement — divide by 1e9 then multiply by 10,000 for per-settlement bps. Positive means longs pay shorts.
Prediction-Market Discovery
Unified Kalshi + Polymarket discovery. Every market comes back in the same shape so you can call quote(market.yes_token_id) without branching on venue.
// Whole open Kalshi universe (~43k rows, ~4 s)
let all = seq.markets("kalshi").active_only(true).fetch_all().await?;
// Manual cursor for streaming/progress
let p1 = seq.markets("kalshi").active_only(true).limit(1000).fetch().await?;
if let Some(c) = p1.next_cursor.as_deref() {
let p2 = seq.markets("kalshi").active_only(true).limit(1000)
.cursor(c).fetch().await?;
}
// Direct slug lookup
let m = seq.market("fed-decision-in-october", "polymarket").await?;For "everything Kalshi/PM ever recorded," pair active=true, closed=true — see Status filters for the full matrix.
Tripwire — order_by: the SDK's previous default of order_by("volume") silently routed every Kalshi call through a 4× over-fetch + client-side rerank that broke cursor invariants — exhaustive walks capped at ~25% of the catalog. The default is now empty (no sort); pass order_by("volume") explicitly only when you want top-by-volume and aren't paginating.
Settlement (prediction markets)
Prediction-market ground truth. resolved_outcome is always what the venue paid — never reconstructed from our Chainlink read.
let s = seq.settlement("btc-updown-5m-1776537600")
.with_underlying()
.fetch().await?;
if s.status == "resolved" {
println!("paid: {:?}", s.resolved_outcome);
}Returns Settlement { slug, question, outcomes, outcome_token_ids, yes_token_id, no_token_id, condition_id, neg_risk, status, resolved_outcome, outcome_source, yes_price, no_price, event_start_s, event_end_s, resolution_source, underlying }.
underlying=true includes the Chainlink curve resampled inside the event window (slower — bounded on-chain RPC). When agrees_with_settlement == false, the on-chain price disagreed with the venue's payout — drop for ML training.
Fixed-point conventions
Every wire payload uses the same scale. The _1e8 / _1e9 / _bps / _ns suffixes are load-bearing:
| Suffix | Scale | Example |
|---|---|---|
_1e8 | × 10⁸ | 1 BTC = 100_000_000, 0.5 ETH = 50_000_000 |
_1e9 | × 10⁹ | $50,000 = 50_000_000_000_000 |
_bps | basis points | 10 bps = 0.10% |
_ns | nanoseconds since Unix epoch | 1705406400000000000 |
| (no suffix) | human float | bid: 73984.57 |
The Rust SDK exposes Qty / Px wrappers that hold both views (Qty::from_human(1.5) → qty.human() == 1.5). The Python SDK keeps responses as plain dicts — convert explicitly: qty = row["qty_1e8"] / 1e8.