Python SDK Reference
Full reference for mackinac-client. For installation and worked examples see the Python SDK overview.
Authentication
| Method | When to use | Tier |
|---|---|---|
Mackinac() | Scripts, demos, notebooks — no credentials needed | Anonymous — 3 symbols / IP, 24 h history |
Mackinac.from_api_key("mk_live_…") | Server-side processes, long-running WS and REST consumers | api — high cap, full history |
Mackinac.from_jwt("eyJ…") | Browser-issued JWT, short-lived sessions | api — full REST history at your subscription tier |
Mackinac.from_token(token) | CLIs and scripts that accept a credential as input | Sniffs the prefix and dispatches to the right path |
Mackinac.from_wallet(privkey, addr) | First-time sign-in flow | One-shot — returns a client holding a fresh JWT |
from_token is the most convenient for scripts: pass MACKINAC_TOKEN and it handles JWT vs. API key detection automatically.
REST methods
All methods are available on both Mackinac (sync) and AsyncClient (async) with the same signatures.
# Discoverym.markets() # dict[str, MarketStatus]m.market("hl") # MarketStatusm.live_symbols("hl") # list[str]m.live_symbols() # dict[str, list[str]]m.historical_symbols("hl") # list[str]m.instruments() # dict[str, InstrumentVenues]m.instrument("ETH") # InstrumentVenues
# History — generators; iterate until exhausted (auto-paginates)for trade in m.history_trades("hl", "ETH", start=..., end=...): ...for snap in m.history_quotes("hl", "ETH", start=..., end=...): ...for fr in m.history_funding("hl", "ETH", start=..., end=...): ...for rate in m.history_rates(pendle_address, start=..., end=...): ...start and end accept ISO 8601 strings, epoch-millisecond integers, or datetime objects (timezone-aware recommended). Pagination is transparent — keep iterating and the generator fetches the next cursor automatically.
history_funding returns FundingRecord objects where ratePct is the annualised funding rate in percent (not bps, not a decimal fraction).
WebSocket message types
The FeedMessage discriminated union resolves to one of these types based on the type field:
| Type | Source venues |
|---|---|
QuoteMessage | Every venue — top-of-book bid/ask updates |
PrintMessage | Every CLOB, AMM, and oracle venue that records trades |
FundingMessage | HL, dYdX, GMX, Vertex, Ostium |
DepthMessage | AMM concentrated-liquidity tick snapshot + market-impact estimates at $1k / $10k / $100k / $1M |
LiquidityMessage | AMM mint (LP add) and burn (LP remove) events |
RateMarketMessage | Pendle and Spectra yield markets — implied APY, PT/YT price, TVL |
RateDepthMessage | Pendle depth-at-size APY |
AmmBookMessage | Consolidated AMM NBBO across all Arbitrum venues (professional tier) |
SpreadMessage | Same-venue cross-fee-tier spread |
ArbFlagMessage | Edge-triggered cross-venue arb open/close (admin tier) |
SnapshotMessage | Initial ring-buffer replay of recent prints on subscribe |
ErrorMessage | Tier gates, cap exhaustion, unknown symbol, etc. |
FeedStaleMessage | Upstream feed has gone stale — data may be delayed |
FeedLiveMessage | Upstream feed has recovered |
Consuming a feed
with m.subscribe("hl:ETH", "uni:WETH/USDC") as feed: for msg in feed: match msg: case PrintMessage(): ... case QuoteMessage(): ... case FundingMessage(): ... case ErrorMessage(): print("server error:", msg.code, msg.message)Filtering by type
Pass one type or a tuple of types to types= and the iterator only yields those:
with m.subscribe("hl:ETH", types=(QuoteMessage, PrintMessage)) as feed: for msg in feed: # PrintMessage and QuoteMessage only; everything else discarded ...Symbol naming
Each venue uses its native symbol convention. Use the helpers in mackinac.symbols to construct them safely:
from mackinac import symbols
symbols.hl_perp("ETH") # "ETH"symbols.hl_spot("PURR", "USDC") # "PURR/USDC"symbols.amm_pair("WETH", "USDC") # "WETH/USDC"symbols.ostium_pair("XAU", "USD") # "XAU/USD"symbols.pendle_address("0xABC…") # lowercased 0x addressSlashes in symbols are URL-encoded automatically on REST history calls. WebSocket subscribe takes them literally.
Reconnect, dedup, and backpressure
These are handled by the engine — you don’t need to implement them:
- Reconnect — exponential backoff (1 s → 2 → … → 30 s cap) on any transport error. All active subscriptions are re-sent on the new connection. The
for/async forloop in your code sees no interruption. - Server-requested reconnect — when the server sends a
server_closingframe, the engine waits the requested delay before reconnecting. - Dedup — print messages are de-duplicated across reconnect-snapshot replays using
(block, txIndex)for on-chain venues and(time, price, size, side)for HL. Duplicate prints never appear in the iterator. - Backpressure — the internal queue caps at 10,000 messages. If a slow consumer fills it, the oldest message is dropped and a warning is logged. Fix: process faster or reduce subscription breadth.
Errors
Library-level exceptions are in mackinac.exceptions:
| Exception | When it’s raised |
|---|---|
AuthError | Bad or expired credentials — 401 from REST, auth_failed on WebSocket |
TierError | Endpoint requires a higher tier — 403, subscription_required |
RateLimitError | 429 — .retry_after attribute gives the suggested wait in seconds |
SymbolLimitError | Tried to subscribe past your symbol cap (also surfaced as ErrorMessage in the stream) |
InvalidSymbolError | Unknown venue or symbol pair |
ServerError | 5xx after three backoff retries |
ConnectionError | Transport-level failure that exhausted reconnect attempts |
MackinacError | Base class — catch this if you don’t need the subtype |
In a WebSocket feed, errors that don’t kill the connection arrive as ErrorMessage objects in the iterator stream (e.g. a single unknown symbol in a multi-symbol subscribe). Errors that do kill the connection — auth failure, tier gate — are raised on the next iteration step.
Versioning
import mackinacprint(mackinac.__version__) # library version, e.g. "0.1.0"print(mackinac.__protocol_version__) # wire-protocol versionDependency pins: pydantic>=2,<3, httpx>=0.27, websockets>=12.
Semver policy: major bump if a server-side field is removed from a message type; minor bump if a field is added. Patch for bug fixes and non-breaking additions.
Examples in the repo
All examples are ≤60 lines and runnable. Pass an API key as the first argument to unlock the higher symbol cap and longer history.
| File | Purpose |
|---|---|
subscribe_quotes.py | Live HL ETH top-of-book |
live_trades.py | Live HL ETH prints, type-filtered |
quotes_and_trades.py | Both message types from one feed |
historical_trades.py | Ostium XAU/USD trade history to CSV |
historical_quotes.py | OHLC reconstruction from quote snapshots |
multi_venue_basis.py | HL / Uni / GMX cross-venue basis |
yield_rates.py | Pendle + Spectra APY table |
rate_table.py | Live rates DataFrame (pandas) |
amm_fee_table.py | AMM depth + fee-tier impact table (pandas) |
reconnect_pattern.py | Iterator surviving a forced disconnect |
funding_carry.py | CME vs HL vs Ostium carry comparison |